feat: enhance UI and functionality for media library management
- Updated video and photo sections in the UI to include detailed card layouts with thumbnails and file information. - Added interactive features to video cards, allowing users to click and play videos in a pop-up player. - Improved sidebar navigation and settings page for better user experience. - Refined global styles and color themes for a cohesive design across the application.
This commit is contained in:
parent
90ba6df611
commit
555a71ffc6
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
description: style use tailwind css v3 version, always comply with v3 syntax
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
@ -19,7 +19,9 @@ UI:
|
||||||
3. sidebar should have these sections: Settings, Videos, Photos, Folder Viewer
|
3. sidebar should have these sections: Settings, Videos, Photos, Folder Viewer
|
||||||
4. Settings section: open manage page in the main area, allow user to add media library path; delete library.
|
4. Settings section: open manage page in the main area, allow user to add media library path; delete library.
|
||||||
5. Videos section: open video cards page in the main area, each card has thumbnail pic displayed. the card lower part display video path, size.
|
5. Videos section: open video cards page in the main area, each card has thumbnail pic displayed. the card lower part display video path, size.
|
||||||
|
5.1. the video card has 2 part, 80% of the upper area shows the thumbnail of the video, 20% of the lower part shows the video path, size in GB/MB, depends on the actual size of the video.
|
||||||
6. photo section: TBD
|
6. photo section: TBD
|
||||||
7. folder viewer: list libraries in the side bar folder viewer section. once one of the folder is selected, display the folder sturcture in the main area. the vdieo or photo display thunbnail and information as the video card would do. for the folder, display the folder icon, which can be entered in
|
7. folder viewer: list libraries in the side bar folder viewer section. once one of the folder is selected, display the folder sturcture in the main area. the vdieo or photo display thunbnail and information as the video card would do. for the folder, display the folder icon, which can be entered in
|
||||||
|
8. the video card can be clicked, once clicked, a poped up video player will be displayed. it can be closed, fast forward, expand to full screen, etc.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,9 @@ UI:
|
||||||
3. sidebar should have these sections: Settings, Videos, Photos, Folder Viewer
|
3. sidebar should have these sections: Settings, Videos, Photos, Folder Viewer
|
||||||
4. Settings section: open manage page in the main area, allow user to add media library path; delete library.
|
4. Settings section: open manage page in the main area, allow user to add media library path; delete library.
|
||||||
5. Videos section: open video cards page in the main area, each card has thumbnail pic displayed. the card lower part display video path, size.
|
5. Videos section: open video cards page in the main area, each card has thumbnail pic displayed. the card lower part display video path, size.
|
||||||
|
5.1. the video card has 2 part, 80% of the upper area shows the thumbnail of the video, 20% of the lower part shows the video path, size in GB/MB, depends on the actual size of the video.
|
||||||
6. photo section: TBD
|
6. photo section: TBD
|
||||||
7. folder viewer: list libraries in the side bar folder viewer section. once one of the folder is selected, display the folder sturcture in the main area. the vdieo or photo display thunbnail and information as the video card would do. for the folder, display the folder icon, which can be entered in
|
7. folder viewer: list libraries in the side bar folder viewer section. once one of the folder is selected, display the folder sturcture in the main area. the vdieo or photo display thunbnail and information as the video card would do. for the folder, display the folder icon, which can be entered in
|
||||||
|
8. the video card can be clicked, once clicked, a poped up video player will be displayed. it can be closed, fast forward, expand to full screen, etc.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
3
PRD.md
3
PRD.md
|
|
@ -1,5 +1,6 @@
|
||||||
Project Description:
|
Project Description:
|
||||||
This is a nextjs project, basically a youtube like video sites.
|
This is a nextjs project, basically a youtube like video sites.
|
||||||
|
the tailwindcss is v4 version. must ensure all the css related tailwind sytling code should comply with the v4 standard
|
||||||
|
|
||||||
Feature requirement:
|
Feature requirement:
|
||||||
1. Has a youtube like UI
|
1. Has a youtube like UI
|
||||||
|
|
@ -19,7 +20,9 @@ UI:
|
||||||
3. sidebar should have these sections: Settings, Videos, Photos, Folder Viewer
|
3. sidebar should have these sections: Settings, Videos, Photos, Folder Viewer
|
||||||
4. Settings section: open manage page in the main area, allow user to add media library path; delete library.
|
4. Settings section: open manage page in the main area, allow user to add media library path; delete library.
|
||||||
5. Videos section: open video cards page in the main area, each card has thumbnail pic displayed. the card lower part display video path, size.
|
5. Videos section: open video cards page in the main area, each card has thumbnail pic displayed. the card lower part display video path, size.
|
||||||
|
5.1. the video card has 2 part, 80% of the upper area shows the thumbnail of the video, 20% of the lower part shows the video path, size in GB/MB, depends on the actual size of the video.
|
||||||
6. photo section: TBD
|
6. photo section: TBD
|
||||||
7. folder viewer: list libraries in the side bar folder viewer section. once one of the folder is selected, display the folder sturcture in the main area. the vdieo or photo display thunbnail and information as the video card would do. for the folder, display the folder icon, which can be entered in
|
7. folder viewer: list libraries in the side bar folder viewer section. once one of the folder is selected, display the folder sturcture in the main area. the vdieo or photo display thunbnail and information as the video card would do. for the folder, display the folder icon, which can be entered in
|
||||||
|
8. the video card can be clicked, once clicked, a poped up video player will be displayed. it can be closed, fast forward, expand to full screen, etc.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -24,12 +24,13 @@
|
||||||
"tailwindcss-animate": "^1.0.7"
|
"tailwindcss-animate": "^1.0.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/postcss": "^4",
|
|
||||||
"@types/better-sqlite3": "^7.6.13",
|
"@types/better-sqlite3": "^7.6.13",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"tailwindcss": "^4",
|
"autoprefixer": "^10.4.21",
|
||||||
|
"postcss": "^8.5.6",
|
||||||
|
"tailwindcss": "^3.4.17",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
|
/** @type {import('postcss-load-config').Config} */
|
||||||
const config = {
|
const config = {
|
||||||
plugins: ["@tailwindcss/postcss"],
|
plugins: {
|
||||||
};
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
export default config;
|
export default config
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ const FolderViewerPage = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800">
|
<div className="min-h-screen bg-zinc-950">
|
||||||
{loading && (
|
{loading && (
|
||||||
<div className="fixed inset-0 bg-black/5 backdrop-blur-sm z-50 flex items-center justify-center">
|
<div className="fixed inset-0 bg-black/5 backdrop-blur-sm z-50 flex items-center justify-center">
|
||||||
<div className="bg-white dark:bg-slate-800 rounded-xl p-6 shadow-xl">
|
<div className="bg-white dark:bg-slate-800 rounded-xl p-6 shadow-xl">
|
||||||
|
|
@ -88,11 +88,11 @@ const FolderViewerPage = () => {
|
||||||
{!path ? (
|
{!path ? (
|
||||||
<div className="flex flex-col items-center justify-center min-h-[60vh]">
|
<div className="flex flex-col items-center justify-center min-h-[60vh]">
|
||||||
<div className="text-center max-w-md">
|
<div className="text-center max-w-md">
|
||||||
<div className="w-20 h-20 bg-gradient-to-br from-blue-500 to-purple-600 rounded-2xl flex items-center justify-center mx-auto mb-6">
|
<div className="w-20 h-20 bg-zinc-900 rounded-2xl flex items-center justify-center mx-auto mb-6 shadow-lg">
|
||||||
<Folder className="h-10 w-10 text-white" />
|
<Folder className="h-10 w-10 text-blue-500" />
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-2xl font-bold text-slate-800 dark:text-slate-100 mb-2">Select a Library</h2>
|
<h2 className="text-2xl font-bold text-white mb-2">Select a Library</h2>
|
||||||
<p className="text-slate-600 dark:text-slate-400">
|
<p className="text-zinc-400">
|
||||||
Choose a media library from the sidebar to browse your files
|
Choose a media library from the sidebar to browse your files
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -100,14 +100,14 @@ const FolderViewerPage = () => {
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<nav className="flex items-center space-x-2 text-sm font-medium text-slate-600 dark:text-slate-400 mb-4">
|
<nav className="flex items-center space-x-2 text-sm font-medium text-zinc-400 mb-4">
|
||||||
<Link href="/folder-viewer" className="hover:text-slate-900 dark:hover:text-slate-200 transition-colors">
|
<Link href="/folder-viewer" className="hover:text-white transition-colors">
|
||||||
Libraries
|
Libraries
|
||||||
</Link>
|
</Link>
|
||||||
<span>/</span>
|
<span>/</span>
|
||||||
<span className="text-slate-900 dark:text-slate-100 font-semibold">{path.split('/').pop()}</span>
|
<span className="text-white font-semibold">{path.split('/').pop()}</span>
|
||||||
</nav>
|
</nav>
|
||||||
<h1 className="text-3xl font-bold bg-gradient-to-r from-slate-900 to-slate-700 dark:from-white dark:to-slate-300 bg-clip-text text-transparent">
|
<h1 className="text-3xl font-bold text-white tracking-tight">
|
||||||
{path.split('/').pop()}
|
{path.split('/').pop()}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -4,57 +4,98 @@
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
--background: 0 0% 98%;
|
--background: 0 0% 100%;
|
||||||
--foreground: 240 10% 3.9%;
|
--foreground: 222.2 84% 4.9%;
|
||||||
--card: 0 0% 100%;
|
--card: 0 0% 100%;
|
||||||
--card-foreground: 240 10% 3.9%;
|
--card-foreground: 222.2 84% 4.9%;
|
||||||
--popover: 0 0% 100%;
|
--popover: 0 0% 100%;
|
||||||
--popover-foreground: 240 10% 3.9%;
|
--popover-foreground: 222.2 84% 4.9%;
|
||||||
--primary: 222.2 47.4% 11.2%;
|
--primary: 221.2 83.2% 53.3%;
|
||||||
--primary-foreground: 210 40% 98%;
|
--primary-foreground: 210 40% 98%;
|
||||||
--secondary: 240 4.8% 95.9%;
|
--secondary: 210 40% 96%;
|
||||||
--secondary-foreground: 240 5.9% 10%;
|
--secondary-foreground: 222.2 84% 4.9%;
|
||||||
--muted: 240 4.8% 95.9%;
|
--muted: 210 40% 96%;
|
||||||
--muted-foreground: 240 3.8% 46.1%;
|
--muted-foreground: 215.4 16.3% 46.9%;
|
||||||
--accent: 240 4.8% 95.9%;
|
--accent: 210 40% 96%;
|
||||||
--accent-foreground: 240 5.9% 10%;
|
--accent-foreground: 222.2 84% 4.9%;
|
||||||
--destructive: 0 84.2% 60.2%;
|
--destructive: 0 84.2% 60.2%;
|
||||||
--destructive-foreground: 0 0% 98%;
|
--destructive-foreground: 210 40% 98%;
|
||||||
--border: 240 5.9% 90%;
|
--border: 214.3 31.8% 91.4%;
|
||||||
--input: 240 5.9% 90%;
|
--input: 214.3 31.8% 91.4%;
|
||||||
--ring: 240 10% 3.9%;
|
--ring: 221.2 83.2% 53.3%;
|
||||||
--radius: 0.5rem;
|
--radius: 0.75rem;
|
||||||
|
--chart-1: 12 76% 61%;
|
||||||
|
--chart-2: 173 58% 39%;
|
||||||
|
--chart-3: 197 37% 24%;
|
||||||
|
--chart-4: 43 74% 66%;
|
||||||
|
--chart-5: 27 87% 67%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--background: 240 10% 3.9%;
|
--background: 222.2 84% 4.9%;
|
||||||
--foreground: 0 0% 98%;
|
--foreground: 210 40% 98%;
|
||||||
--card: 240 10% 3.9%;
|
--card: 222.2 84% 4.9%;
|
||||||
--card-foreground: 0 0% 98%;
|
--card-foreground: 210 40% 98%;
|
||||||
--popover: 240 10% 3.9%;
|
--popover: 222.2 84% 4.9%;
|
||||||
--popover-foreground: 0 0% 98%;
|
--popover-foreground: 210 40% 98%;
|
||||||
--primary: 210 40% 98%;
|
--primary: 217.2 91.2% 59.8%;
|
||||||
--primary-foreground: 222.2 47.4% 11.2%;
|
--primary-foreground: 222.2 84% 4.9%;
|
||||||
--secondary: 240 3.7% 15.9%;
|
--secondary: 217.2 32.6% 17.5%;
|
||||||
--secondary-foreground: 0 0% 98%;
|
--secondary-foreground: 210 40% 98%;
|
||||||
--muted: 240 3.7% 15.9%;
|
--muted: 217.2 32.6% 17.5%;
|
||||||
--muted-foreground: 240 5% 64.9%;
|
--muted-foreground: 215 20.2% 65.1%;
|
||||||
--accent: 240 3.7% 15.9%;
|
--accent: 217.2 32.6% 17.5%;
|
||||||
--accent-foreground: 0 0% 98%;
|
--accent-foreground: 210 40% 98%;
|
||||||
--destructive: 0 62.8% 30.6%;
|
--destructive: 0 62.8% 30.6%;
|
||||||
--destructive-foreground: 0 0% 98%;
|
--destructive-foreground: 210 40% 98%;
|
||||||
--border: 240 3.7% 15.9%;
|
--border: 217.2 32.6% 17.5%;
|
||||||
--input: 240 3.7% 15.9%;
|
--input: 217.2 32.6% 17.5%;
|
||||||
--ring: 215 20.2% 65.1%;
|
--ring: 224.3 76.3% 94.1%;
|
||||||
|
--chart-1: 220 70% 50%;
|
||||||
|
--chart-2: 160 60% 45%;
|
||||||
|
--chart-3: 30 80% 55%;
|
||||||
|
--chart-4: 280 65% 60%;
|
||||||
|
--chart-5: 340 75% 55%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
* {
|
* {
|
||||||
border-color: hsl(var(--border));
|
@apply border-border;
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
background-color: hsl(var(--background));
|
@apply bg-background text-foreground;
|
||||||
color: hsl(var(--foreground));
|
font-feature-settings: "rlig" 1, "calt" 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@layer utilities {
|
||||||
|
.text-balance {
|
||||||
|
text-wrap: balance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom scrollbar */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
@apply bg-muted/50;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
@apply bg-muted-foreground/30 rounded-full;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
@apply bg-muted-foreground/50;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Smooth transitions */
|
||||||
|
* {
|
||||||
|
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
|
||||||
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
transition-duration: 150ms;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@ const inter = Inter({
|
||||||
});
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Create Next App",
|
title: "NextAV - Modern Media Library",
|
||||||
description: "Generated by create next app",
|
description: "A beautiful and modern media library for your videos and photos",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
|
|
@ -19,13 +19,15 @@ export default function RootLayout({
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en" className="dark">
|
||||||
<body
|
<body
|
||||||
className={`${inter.variable} antialiased bg-gray-100 dark:bg-gray-900`}
|
className={`${inter.variable} antialiased bg-background text-foreground`}
|
||||||
>
|
>
|
||||||
<div className="flex h-screen">
|
<div className="flex h-screen bg-gradient-to-br from-background via-background to-muted/20">
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
<main className="flex-1 p-4 overflow-y-auto">{children}</main>
|
<main className="flex-1 overflow-y-auto bg-background/50 backdrop-blur-sm">
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
161
src/app/page.tsx
161
src/app/page.tsx
|
|
@ -1,12 +1,163 @@
|
||||||
import { Header } from "@/components/ui/header";
|
import { Header } from "@/components/ui/header";
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Play, Image, Folder, Settings, ArrowRight } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto px-4 py-8">
|
<div className="min-h-screen p-6">
|
||||||
<Header title="Home" />
|
<div className="max-w-6xl mx-auto">
|
||||||
<div className="flex flex-col items-center justify-center mt-8">
|
{/* Hero Section */}
|
||||||
<h2 className="text-xl font-semibold">Welcome to NextAV</h2>
|
<div className="text-center mb-12">
|
||||||
<p className="text-gray-500 dark:text-gray-400">Select a library from the sidebar to get started.</p>
|
<div className="inline-flex items-center justify-center w-16 h-16 bg-gradient-to-br from-primary to-primary/80 rounded-2xl mb-6 shadow-lg">
|
||||||
|
<Play className="h-8 w-8 text-primary-foreground" />
|
||||||
|
</div>
|
||||||
|
<h1 className="text-4xl md:text-5xl font-bold text-foreground mb-4 tracking-tight">
|
||||||
|
Welcome to NextAV
|
||||||
|
</h1>
|
||||||
|
<p className="text-xl text-muted-foreground max-w-2xl mx-auto leading-relaxed">
|
||||||
|
Your modern media library for organizing and enjoying videos and photos with a beautiful, intuitive interface.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Quick Actions Grid */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-12">
|
||||||
|
<Link href="/videos">
|
||||||
|
<Card className="group hover:shadow-lg transition-all duration-300 hover:-translate-y-1 cursor-pointer border-border">
|
||||||
|
<CardHeader className="pb-3">
|
||||||
|
<div className="w-12 h-12 bg-gradient-to-br from-red-500 to-red-600 rounded-xl flex items-center justify-center mb-2 group-hover:scale-110 transition-transform">
|
||||||
|
<Play className="h-6 w-6 text-white" />
|
||||||
|
</div>
|
||||||
|
<CardTitle className="text-lg">Videos</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Browse and play your video collection
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="flex items-center text-sm text-muted-foreground group-hover:text-primary transition-colors">
|
||||||
|
<span>View videos</span>
|
||||||
|
<ArrowRight className="h-4 w-4 ml-2 group-hover:translate-x-1 transition-transform" />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link href="/photos">
|
||||||
|
<Card className="group hover:shadow-lg transition-all duration-300 hover:-translate-y-1 cursor-pointer border-border">
|
||||||
|
<CardHeader className="pb-3">
|
||||||
|
<div className="w-12 h-12 bg-gradient-to-br from-green-500 to-green-600 rounded-xl flex items-center justify-center mb-2 group-hover:scale-110 transition-transform">
|
||||||
|
<Image className="h-6 w-6 text-white" />
|
||||||
|
</div>
|
||||||
|
<CardTitle className="text-lg">Photos</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Explore your photo gallery
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="flex items-center text-sm text-muted-foreground group-hover:text-primary transition-colors">
|
||||||
|
<span>View photos</span>
|
||||||
|
<ArrowRight className="h-4 w-4 ml-2 group-hover:translate-x-1 transition-transform" />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link href="/folder-viewer">
|
||||||
|
<Card className="group hover:shadow-lg transition-all duration-300 hover:-translate-y-1 cursor-pointer border-border">
|
||||||
|
<CardHeader className="pb-3">
|
||||||
|
<div className="w-12 h-12 bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl flex items-center justify-center mb-2 group-hover:scale-110 transition-transform">
|
||||||
|
<Folder className="h-6 w-6 text-white" />
|
||||||
|
</div>
|
||||||
|
<CardTitle className="text-lg">Folder Viewer</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Browse your media libraries
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="flex items-center text-sm text-muted-foreground group-hover:text-primary transition-colors">
|
||||||
|
<span>Browse files</span>
|
||||||
|
<ArrowRight className="h-4 w-4 ml-2 group-hover:translate-x-1 transition-transform" />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link href="/settings">
|
||||||
|
<Card className="group hover:shadow-lg transition-all duration-300 hover:-translate-y-1 cursor-pointer border-border">
|
||||||
|
<CardHeader className="pb-3">
|
||||||
|
<div className="w-12 h-12 bg-gradient-to-br from-purple-500 to-purple-600 rounded-xl flex items-center justify-center mb-2 group-hover:scale-110 transition-transform">
|
||||||
|
<Settings className="h-6 w-6 text-white" />
|
||||||
|
</div>
|
||||||
|
<CardTitle className="text-lg">Settings</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Manage your media libraries
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="flex items-center text-sm text-muted-foreground group-hover:text-primary transition-colors">
|
||||||
|
<span>Configure</span>
|
||||||
|
<ArrowRight className="h-4 w-4 ml-2 group-hover:translate-x-1 transition-transform" />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Getting Started Section */}
|
||||||
|
<Card className="border-border bg-gradient-to-br from-muted to-muted">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-xl">Getting Started</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Set up your media library to start organizing your content
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="w-8 h-8 bg-primary rounded-full flex items-center justify-center flex-shrink-0 mt-0.5">
|
||||||
|
<span className="text-sm font-semibold text-primary">1</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-medium text-foreground mb-1">Add Media Libraries</h3>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Go to Settings and add the paths to your media folders (e.g., /mnt/media)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="w-8 h-8 bg-primary rounded-full flex items-center justify-center flex-shrink-0 mt-0.5">
|
||||||
|
<span className="text-sm font-semibold text-primary">2</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-medium text-foreground mb-1">Scan for Media</h3>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Use the scanner to discover videos and photos in your libraries
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="w-8 h-8 bg-primary rounded-full flex items-center justify-center flex-shrink-0 mt-0.5">
|
||||||
|
<span className="text-sm font-semibold text-primary">3</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-medium text-foreground mb-1">Start Browsing</h3>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Explore your media through the Videos, Photos, or Folder Viewer sections
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-6">
|
||||||
|
<Link href="/settings">
|
||||||
|
<Button className="w-full sm:w-auto">
|
||||||
|
<Settings className="h-4 w-4 mr-2" />
|
||||||
|
Go to Settings
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -45,55 +45,59 @@ export default function PhotosPage() {
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800 flex items-center justify-center">
|
<div className="min-h-screen bg-zinc-950 flex items-center justify-center">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="w-16 h-16 bg-gradient-to-br from-purple-500 to-pink-500 rounded-2xl flex items-center justify-center mx-auto mb-4 animate-pulse">
|
<div className="w-16 h-16 bg-purple-600 rounded-2xl flex items-center justify-center mx-auto mb-4 animate-pulse shadow-lg shadow-purple-600/20">
|
||||||
<ImageIcon className="h-8 w-8 text-white" />
|
<ImageIcon className="h-8 w-8 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<p className="text-slate-600 dark:text-slate-400 font-medium">Loading photos...</p>
|
<p className="text-zinc-400 font-medium">Loading photos...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800">
|
<div className="min-h-screen bg-zinc-950">
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<h1 className="text-4xl font-bold bg-gradient-to-r from-purple-600 to-pink-600 bg-clip-text text-transparent mb-2">
|
<h1 className="text-5xl font-bold text-white tracking-tight mb-2">
|
||||||
Photos
|
Photos
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-slate-600 dark:text-slate-400">
|
<p className="text-zinc-400 text-lg">
|
||||||
{photos.length} {photos.length === 1 ? 'photo' : 'photos'} found
|
{photos.length} {photos.length === 1 ? 'photo' : 'photos'} found
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{photos.length > 0 ? (
|
{photos.length > 0 ? (
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 2xl:grid-cols-7 gap-4">
|
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 2xl:grid-cols-7 gap-6">
|
||||||
{photos.map((photo) => (
|
{photos.map((photo) => (
|
||||||
<div key={photo.id}
|
<div key={photo.id}
|
||||||
className="group relative bg-white dark:bg-slate-800 rounded-xl shadow-sm hover:shadow-xl transition-all duration-300 hover:-translate-y-1 overflow-hidden cursor-pointer">
|
className="group relative bg-zinc-900 rounded-lg overflow-hidden cursor-pointer transition-all duration-300 hover:scale-105 hover:shadow-2xl hover:shadow-purple-600/20">
|
||||||
<div className="aspect-square relative overflow-hidden">
|
<div className="aspect-square relative overflow-hidden">
|
||||||
<img
|
<img
|
||||||
src={photo.thumbnail}
|
src={photo.thumbnail}
|
||||||
alt={photo.title}
|
alt={photo.title}
|
||||||
className="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"
|
className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110"
|
||||||
onError={(e) => {
|
onError={(e) => {
|
||||||
(e.target as HTMLImageElement).src = '/placeholder-image.jpg';
|
(e.target as HTMLImageElement).src = '/placeholder-image.jpg';
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
|
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-black/40 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
|
||||||
<div className="absolute bottom-2 left-2 opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
|
||||||
<div className="bg-white/90 backdrop-blur-sm rounded-full p-2 shadow-lg">
|
<div className="absolute bottom-0 left-0 right-0 p-4 transform translate-y-full group-hover:translate-y-0 transition-transform duration-300">
|
||||||
<ImageIcon className="h-4 w-4 text-slate-800" />
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-8 h-8 bg-purple-600 rounded-full flex items-center justify-center">
|
||||||
|
<ImageIcon className="h-4 w-4 text-white" />
|
||||||
|
</div>
|
||||||
|
<span className="text-white text-sm font-medium">View</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-3">
|
<div className="p-4">
|
||||||
<p className="text-sm font-semibold text-slate-900 dark:text-slate-100 truncate mb-1">
|
<p className="text-white font-medium text-sm line-clamp-2 mb-1">
|
||||||
{photo.title}
|
{photo.title}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-slate-600 dark:text-slate-400">
|
<p className="text-zinc-400 text-xs">
|
||||||
{formatFileSize(photo.size)}
|
{formatFileSize(photo.size)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -103,17 +107,17 @@ export default function PhotosPage() {
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center py-20">
|
<div className="text-center py-20">
|
||||||
<div className="max-w-sm mx-auto">
|
<div className="max-w-sm mx-auto">
|
||||||
<div className="w-20 h-20 bg-gradient-to-br from-purple-100 to-pink-100 dark:from-purple-900/20 dark:to-pink-900/20 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
<div className="w-20 h-20 bg-zinc-900 rounded-2xl flex items-center justify-center mx-auto mb-4 shadow-lg">
|
||||||
<ImageIcon className="h-10 w-10 text-purple-600 dark:text-purple-400" />
|
<ImageIcon className="h-10 w-10 text-purple-500" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-semibold text-slate-700 dark:text-slate-300 mb-2">
|
<h3 className="text-2xl font-semibold text-white mb-2">
|
||||||
No Photos Found
|
No Photos Found
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm text-slate-500 dark:text-slate-400 mb-4">
|
<p className="text-zinc-400 mb-6">
|
||||||
Add media libraries to scan for photos
|
Add media libraries to scan for photos
|
||||||
</p>
|
</p>
|
||||||
<Link href="/settings">
|
<Link href="/settings">
|
||||||
<button className="bg-gradient-to-r from-purple-600 to-pink-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:shadow-lg transition-shadow">
|
<button className="bg-purple-600 hover:bg-purple-700 text-white px-6 py-3 rounded-lg text-sm font-medium transition-colors shadow-lg shadow-purple-600/20">
|
||||||
Add Library
|
Add Library
|
||||||
</button>
|
</button>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
||||||
|
|
@ -112,27 +112,27 @@ const SettingsPage = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800">
|
<div className="min-h-screen bg-zinc-950">
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<h1 className="text-4xl font-bold bg-gradient-to-r from-slate-900 to-slate-700 dark:from-white dark:to-slate-300 bg-clip-text text-transparent mb-2">
|
<h1 className="text-5xl font-bold text-white tracking-tight mb-2">
|
||||||
Settings
|
Settings
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-slate-600 dark:text-slate-400">
|
<p className="text-zinc-400 text-lg">
|
||||||
Configure your media libraries and system preferences
|
Configure your media libraries and system preferences
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||||
<div className="lg:col-span-2 space-y-8">
|
<div className="lg:col-span-2 space-y-8">
|
||||||
<div className="bg-white dark:bg-slate-800 rounded-2xl shadow-sm border border-slate-200 dark:border-slate-700 p-6">
|
<div className="bg-zinc-900 rounded-xl border border-zinc-800 p-6">
|
||||||
<div className="flex items-center gap-3 mb-6">
|
<div className="flex items-center gap-3 mb-6">
|
||||||
<div className="w-10 h-10 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-xl flex items-center justify-center">
|
<div className="w-10 h-10 bg-red-600 rounded-xl flex items-center justify-center shadow-lg shadow-red-600/20">
|
||||||
<Folder className="h-5 w-5 text-white" />
|
<Folder className="h-5 w-5 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-xl font-bold text-slate-900 dark:text-slate-100">Media Libraries</h2>
|
<h2 className="text-xl font-bold text-white">Media Libraries</h2>
|
||||||
<p className="text-sm text-slate-600 dark:text-slate-400">Manage your media source directories</p>
|
<p className="text-sm text-zinc-400">Manage your media source directories</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -147,12 +147,12 @@ const SettingsPage = () => {
|
||||||
setError(null);
|
setError(null);
|
||||||
}}
|
}}
|
||||||
onKeyPress={(e) => e.key === 'Enter' && addLibrary()}
|
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"
|
className="flex-1 px-4 py-3 bg-zinc-800 border border-zinc-700 rounded-lg text-sm text-white focus:outline-none focus:ring-2 focus:ring-red-500 focus:border-transparent transition-all placeholder-zinc-500"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={addLibrary}
|
onClick={addLibrary}
|
||||||
disabled={!newLibraryPath.trim()}
|
disabled={!newLibraryPath.trim()}
|
||||||
className="px-4 py-3 bg-gradient-to-r from-blue-500 to-indigo-600 text-white font-medium rounded-xl hover:shadow-lg transition-all disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
|
className="px-4 py-3 bg-red-600 text-white font-medium rounded-lg hover:bg-red-700 transition-all disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2 shadow-lg shadow-red-600/20"
|
||||||
>
|
>
|
||||||
<Plus size={16} />
|
<Plus size={16} />
|
||||||
Add
|
Add
|
||||||
|
|
@ -160,31 +160,31 @@ const SettingsPage = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<div className="p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-xl">
|
<div className="p-3 bg-red-900/20 border border-red-800 rounded-lg">
|
||||||
<p className="text-sm text-red-600 dark:text-red-400">{error}</p>
|
<p className="text-sm text-red-400">{error}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{libraries.length > 0 && (
|
{libraries.length > 0 && (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<h3 className="text-sm font-semibold text-slate-700 dark:text-slate-300 uppercase tracking-wider">
|
<h3 className="text-sm font-semibold text-zinc-400 uppercase tracking-wider">
|
||||||
{libraries.length} {libraries.length === 1 ? 'Library' : 'Libraries'}
|
{libraries.length} {libraries.length === 1 ? 'Library' : 'Libraries'}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{libraries.map((lib) => (
|
{libraries.map((lib) => (
|
||||||
<div key={lib.id} className="flex items-center justify-between p-4 bg-slate-50 dark:bg-slate-900/50 rounded-xl border border-slate-200 dark:border-slate-700 group hover:border-slate-300 dark:hover:border-slate-600 transition-all">
|
<div key={lib.id} className="flex items-center justify-between p-4 bg-zinc-800 rounded-lg border border-zinc-700 group hover:border-zinc-600 transition-all">
|
||||||
<div className="flex items-center gap-3 flex-1 min-w-0">
|
<div className="flex items-center gap-3 flex-1 min-w-0">
|
||||||
<div className="w-8 h-8 bg-gradient-to-br from-slate-400 to-slate-600 rounded-lg flex items-center justify-center">
|
<div className="w-8 h-8 bg-zinc-700 rounded-lg flex items-center justify-center">
|
||||||
<HardDrive className="h-4 w-4 text-white" />
|
<HardDrive className="h-4 w-4 text-zinc-300" />
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<p className="text-sm font-mono text-slate-900 dark:text-slate-100 truncate">{lib.path}</p>
|
<p className="text-sm font-mono text-zinc-100 truncate">{lib.path}</p>
|
||||||
<p className="text-xs text-slate-500 dark:text-slate-400">Ready to scan</p>
|
<p className="text-xs text-zinc-500">Ready to scan</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => deleteLibrary(lib.id)}
|
onClick={() => deleteLibrary(lib.id)}
|
||||||
className="p-2 text-slate-400 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-lg transition-all"
|
className="p-2 text-zinc-400 hover:text-red-500 hover:bg-red-900/20 rounded-lg transition-all"
|
||||||
>
|
>
|
||||||
<Trash2 size={16} />
|
<Trash2 size={16} />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -196,26 +196,26 @@ const SettingsPage = () => {
|
||||||
|
|
||||||
{libraries.length === 0 && (
|
{libraries.length === 0 && (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
<div className="w-16 h-16 bg-slate-100 dark:bg-slate-800 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
<div className="w-16 h-16 bg-zinc-800 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
||||||
<Folder className="h-8 w-8 text-slate-400" />
|
<Folder className="h-8 w-8 text-zinc-500" />
|
||||||
</div>
|
</div>
|
||||||
<p className="text-slate-600 dark:text-slate-400">No libraries configured</p>
|
<p className="text-zinc-400">No libraries configured</p>
|
||||||
<p className="text-sm text-slate-500 dark:text-slate-500 mt-1">Add your first library to get started</p>
|
<p className="text-sm text-zinc-500 mt-1">Add your first library to get started</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-white dark:bg-slate-800 rounded-2xl shadow-sm border border-slate-200 dark:border-slate-700 p-6">
|
<div className="bg-zinc-900 rounded-xl border border-zinc-800 p-6">
|
||||||
<div className="flex items-center gap-3 mb-4">
|
<div className="flex items-center gap-3 mb-4">
|
||||||
<div className="w-10 h-10 bg-gradient-to-br from-green-500 to-emerald-600 rounded-xl flex items-center justify-center">
|
<div className="w-10 h-10 bg-green-600 rounded-xl flex items-center justify-center shadow-lg shadow-green-600/20">
|
||||||
<svg className="h-5 w-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="h-5 w-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-xl font-bold text-slate-900 dark:text-slate-100">Media Scanner</h2>
|
<h2 className="text-xl font-bold text-white">Media Scanner</h2>
|
||||||
<p className="text-sm text-slate-600 dark:text-slate-400">Discover new media files</p>
|
<p className="text-sm text-zinc-400">Discover new media files</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -236,14 +236,14 @@ const SettingsPage = () => {
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{scanStatus && (
|
{scanStatus && (
|
||||||
<div className={`p-3 rounded-xl ${scanStatus.includes('success') ? 'bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800' : 'bg-slate-50 dark:bg-slate-900/50 border border-slate-200 dark:border-slate-700'}`}>
|
<div className={`p-3 rounded-lg ${scanStatus.includes('success') ? 'bg-green-900/20 border border-green-800' : 'bg-zinc-800 border border-zinc-700'}`}>
|
||||||
<p className={`text-sm ${scanStatus.includes('success') ? 'text-green-600 dark:text-green-400' : 'text-slate-600 dark:text-slate-400'}`}>
|
<p className={`text-sm ${scanStatus.includes('success') ? 'text-green-400' : 'text-zinc-400'}`}>
|
||||||
{scanStatus}
|
{scanStatus}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<p className="text-sm text-slate-500 dark:text-slate-400 text-center">
|
<p className="text-sm text-zinc-500 text-center">
|
||||||
{libraries.length === 0
|
{libraries.length === 0
|
||||||
? "Add at least one library to enable scanning"
|
? "Add at least one library to enable scanning"
|
||||||
: "Scan will discover new videos and photos"}
|
: "Scan will discover new videos and photos"}
|
||||||
|
|
@ -253,34 +253,34 @@ const SettingsPage = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="bg-white dark:bg-slate-800 rounded-2xl shadow-sm border border-slate-200 dark:border-slate-700 p-6">
|
<div className="bg-zinc-900 rounded-xl border border-zinc-800 p-6">
|
||||||
<h3 className="text-lg font-bold text-slate-900 dark:text-slate-100 mb-4">System Status</h3>
|
<h3 className="text-lg font-bold text-white mb-4">System Status</h3>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex justify-between items-center p-3 bg-slate-50 dark:bg-slate-900/50 rounded-lg">
|
<div className="flex justify-between items-center p-3 bg-zinc-800 rounded-lg">
|
||||||
<span className="text-sm font-medium text-slate-600 dark:text-slate-400">Libraries</span>
|
<span className="text-sm font-medium text-zinc-400">Libraries</span>
|
||||||
<span className="text-sm font-bold text-slate-900 dark:text-slate-100">{libraries.length}</span>
|
<span className="text-sm font-bold text-white">{libraries.length}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center p-3 bg-slate-50 dark:bg-slate-900/50 rounded-lg">
|
<div className="flex justify-between items-center p-3 bg-zinc-800 rounded-lg">
|
||||||
<span className="text-sm font-medium text-slate-600 dark:text-slate-400">Database</span>
|
<span className="text-sm font-medium text-zinc-400">Database</span>
|
||||||
<span className="text-sm font-bold text-slate-900 dark:text-slate-100">SQLite</span>
|
<span className="text-sm font-bold text-white">SQLite</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center p-3 bg-slate-50 dark:bg-slate-900/50 rounded-lg">
|
<div className="flex justify-between items-center p-3 bg-zinc-800 rounded-lg">
|
||||||
<span className="text-sm font-medium text-slate-600 dark:text-slate-400">Status</span>
|
<span className="text-sm font-medium text-zinc-400">Status</span>
|
||||||
<span className="text-sm font-bold text-green-600 dark:text-green-400">Active</span>
|
<span className="text-sm font-bold text-green-400">Active</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-white dark:bg-slate-800 rounded-2xl shadow-sm border border-slate-200 dark:border-slate-700 p-6">
|
<div className="bg-zinc-900 rounded-xl border border-zinc-800 p-6">
|
||||||
<h3 className="text-lg font-bold text-slate-900 dark:text-slate-100 mb-4">Quick Actions</h3>
|
<h3 className="text-lg font-bold text-white mb-4">Quick Actions</h3>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Link href="/videos" className="block w-full px-3 py-2 text-sm text-slate-700 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-700 rounded-lg transition-colors">
|
<Link href="/videos" className="block w-full px-3 py-2 text-sm text-zinc-300 hover:bg-zinc-800 rounded-lg transition-colors">
|
||||||
View Videos
|
View Videos
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="/photos" className="block w-full px-3 py-2 text-sm text-slate-700 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-700 rounded-lg transition-colors">
|
<Link href="/photos" className="block w-full px-3 py-2 text-sm text-zinc-300 hover:bg-zinc-800 rounded-lg transition-colors">
|
||||||
View Photos
|
View Photos
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="/folder-viewer" className="block w-full px-3 py-2 text-sm text-slate-700 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-700 rounded-lg transition-colors">
|
<Link href="/folder-viewer" className="block w-full px-3 py-2 text-sm text-zinc-300 hover:bg-zinc-800 rounded-lg transition-colors">
|
||||||
Browse Files
|
Browse Files
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,10 @@
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Film } from "lucide-react";
|
import { Film, Play, Clock, HardDrive, Search, Filter } from "lucide-react";
|
||||||
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
|
||||||
interface Video {
|
interface Video {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
@ -16,6 +19,7 @@ interface Video {
|
||||||
const VideosPage = () => {
|
const VideosPage = () => {
|
||||||
const [videos, setVideos] = useState<Video[]>([]);
|
const [videos, setVideos] = useState<Video[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchVideos();
|
fetchVideos();
|
||||||
|
|
@ -41,79 +45,150 @@ const VideosPage = () => {
|
||||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const filteredVideos = videos.filter(video =>
|
||||||
|
video.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
|
video.path.toLowerCase().includes(searchTerm.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800 flex items-center justify-center">
|
<div className="min-h-screen p-6">
|
||||||
|
<div className="max-w-7xl mx-auto">
|
||||||
|
<div className="flex items-center justify-center min-h-[60vh]">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="w-16 h-16 bg-gradient-to-br from-red-500 to-orange-500 rounded-2xl flex items-center justify-center mx-auto mb-4 animate-pulse">
|
<div className="w-16 h-16 bg-gradient-to-br from-primary to-primary/80 rounded-2xl flex items-center justify-center mx-auto mb-4 animate-pulse shadow-lg">
|
||||||
<Film className="h-8 w-8 text-white" />
|
<Film className="h-8 w-8 text-primary-foreground" />
|
||||||
|
</div>
|
||||||
|
<p className="text-muted-foreground font-medium">Loading videos...</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-slate-600 dark:text-slate-400 font-medium">Loading videos...</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800">
|
<div className="min-h-screen p-6">
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
<div className="max-w-7xl mx-auto">
|
||||||
|
{/* Header */}
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<h1 className="text-4xl font-bold bg-gradient-to-r from-red-600 to-orange-600 bg-clip-text text-transparent mb-2">
|
<div className="flex items-center gap-4 mb-4">
|
||||||
|
<div className="w-12 h-12 bg-gradient-to-br from-red-500 to-red-600 rounded-xl flex items-center justify-center shadow-lg">
|
||||||
|
<Film className="h-6 w-6 text-white" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl font-bold text-foreground tracking-tight">
|
||||||
Videos
|
Videos
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-slate-600 dark:text-slate-400">
|
<p className="text-muted-foreground">
|
||||||
{videos.length} {videos.length === 1 ? 'video' : 'videos'} found
|
{videos.length} {videos.length === 1 ? 'video' : 'videos'} in your library
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{videos.length > 0 ? (
|
{/* Search and Filter Bar */}
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 2xl:grid-cols-7 gap-4">
|
<div className="flex flex-col sm:flex-row gap-4">
|
||||||
{videos.map((video) => (
|
<div className="relative flex-1">
|
||||||
<div key={video.id}
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||||
className="group relative bg-white dark:bg-slate-800 rounded-xl shadow-sm hover:shadow-xl transition-all duration-300 hover:-translate-y-1 overflow-hidden cursor-pointer">
|
<Input
|
||||||
<div className="aspect-video relative overflow-hidden">
|
placeholder="Search videos..."
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
|
className="pl-10 bg-background border-border"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button variant="outline" className="shrink-0">
|
||||||
|
<Filter className="h-4 w-4 mr-2" />
|
||||||
|
Filter
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Videos Grid */}
|
||||||
|
{filteredVideos.length > 0 ? (
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6 gap-6">
|
||||||
|
{filteredVideos.map((video) => (
|
||||||
|
<Card key={video.id} className="group hover:shadow-lg transition-all duration-300 hover:-translate-y-1 cursor-pointer border-border overflow-hidden">
|
||||||
|
<div className="aspect-video relative overflow-hidden bg-muted">
|
||||||
<img
|
<img
|
||||||
src={video.thumbnail || "/placeholder.svg"}
|
src={video.thumbnail || "/placeholder.svg"}
|
||||||
alt={video.title}
|
alt={video.title}
|
||||||
className="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"
|
className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105"
|
||||||
onError={(e) => {
|
onError={(e) => {
|
||||||
(e.target as HTMLImageElement).src = '/placeholder.svg';
|
(e.target as HTMLImageElement).src = '/placeholder.svg';
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
|
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
|
||||||
<div className="absolute bottom-2 left-2 opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
|
||||||
<div className="bg-white/90 backdrop-blur-sm rounded-full p-2 shadow-lg">
|
{/* Play Button Overlay */}
|
||||||
<Film className="h-4 w-4 text-slate-800" />
|
<div className="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
||||||
|
<div className="w-12 h-12 bg-white/90 backdrop-blur-sm rounded-full flex items-center justify-center shadow-lg">
|
||||||
|
<Play className="h-5 w-5 text-foreground ml-0.5" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Video Type Badge */}
|
||||||
|
<div className="absolute top-2 right-2">
|
||||||
|
<div className="bg-black/70 backdrop-blur-sm rounded-full px-2 py-1">
|
||||||
|
<Film className="h-3 w-3 text-white" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-3">
|
|
||||||
<p className="text-sm font-semibold text-slate-900 dark:text-slate-100 truncate mb-1">
|
<CardContent className="p-4">
|
||||||
|
<h3 className="font-medium text-foreground text-sm line-clamp-2 mb-2 group-hover:text-primary transition-colors">
|
||||||
{video.title}
|
{video.title}
|
||||||
</p>
|
</h3>
|
||||||
<p className="text-xs text-slate-600 dark:text-slate-400">
|
<div className="flex items-center gap-4 text-xs text-muted-foreground">
|
||||||
{formatFileSize(video.size)}
|
<div className="flex items-center gap-1">
|
||||||
</p>
|
<HardDrive className="h-3 w-3" />
|
||||||
|
<span>{formatFileSize(video.size)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground mt-2 line-clamp-1">
|
||||||
|
{video.path}
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
) : searchTerm ? (
|
||||||
|
<div className="text-center py-20">
|
||||||
|
<div className="max-w-sm mx-auto">
|
||||||
|
<div className="w-16 h-16 bg-muted rounded-2xl flex items-center justify-center mx-auto mb-4">
|
||||||
|
<Search className="h-8 w-8 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-xl font-semibold text-foreground mb-2">
|
||||||
|
No videos found
|
||||||
|
</h3>
|
||||||
|
<p className="text-muted-foreground mb-4">
|
||||||
|
Try adjusting your search terms
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setSearchTerm("")}
|
||||||
|
>
|
||||||
|
Clear search
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center py-20">
|
<div className="text-center py-20">
|
||||||
<div className="max-w-sm mx-auto">
|
<div className="max-w-sm mx-auto">
|
||||||
<div className="w-20 h-20 bg-gradient-to-br from-red-100 to-orange-100 dark:from-red-900/20 dark:to-orange-900/20 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
<div className="w-16 h-16 bg-muted rounded-2xl flex items-center justify-center mx-auto mb-4">
|
||||||
<Film className="h-10 w-10 text-red-600 dark:text-red-400" />
|
<Film className="h-8 w-8 text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-semibold text-slate-700 dark:text-slate-300 mb-2">
|
<h3 className="text-xl font-semibold text-foreground mb-2">
|
||||||
No Videos Found
|
No Videos Found
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm text-slate-500 dark:text-slate-400 mb-4">
|
<p className="text-muted-foreground mb-6">
|
||||||
Add media libraries to scan for videos
|
Add media libraries and scan for videos to get started
|
||||||
</p>
|
</p>
|
||||||
<Link href="/settings">
|
<Link href="/settings">
|
||||||
<button className="bg-gradient-to-r from-red-600 to-orange-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:shadow-lg transition-shadow">
|
<Button>
|
||||||
|
<Film className="h-4 w-4 mr-2" />
|
||||||
Add Library
|
Add Library
|
||||||
</button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ import {
|
||||||
Folder,
|
Folder,
|
||||||
Film,
|
Film,
|
||||||
Image as ImageIcon,
|
Image as ImageIcon,
|
||||||
|
Play,
|
||||||
|
FolderOpen,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { usePathname, useSearchParams } from "next/navigation";
|
import { usePathname, useSearchParams } from "next/navigation";
|
||||||
|
|
@ -53,25 +55,25 @@ const SidebarContent = () => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-col bg-white dark:bg-slate-900 border-r border-slate-200 dark:border-slate-800 transition-all duration-300 ease-in-out",
|
"flex flex-col bg-card border-r border-border transition-all duration-300 ease-in-out shadow-lg",
|
||||||
isCollapsed ? "w-20" : "w-72"
|
isCollapsed ? "w-16" : "w-64"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between p-4 border-b border-slate-200 dark:border-slate-800">
|
<div className="flex items-center justify-between p-4 border-b border-border">
|
||||||
{!isCollapsed && (
|
{!isCollapsed && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-8 h-8 bg-gradient-to-br from-blue-500 to-purple-600 rounded-lg flex items-center justify-center">
|
<div className="w-8 h-8 bg-gradient-to-br from-primary to-primary/80 rounded-lg flex items-center justify-center shadow-lg">
|
||||||
<div className="w-4 h-4 bg-white rounded-sm" />
|
<Play className="h-4 w-4 text-primary-foreground" />
|
||||||
</div>
|
</div>
|
||||||
<h1 className="text-lg font-bold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">NextAV</h1>
|
<h1 className="text-lg font-bold text-foreground tracking-tight">NextAV</h1>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
onClick={toggleSidebar}
|
onClick={toggleSidebar}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="hover:bg-slate-100 dark:hover:bg-slate-800 rounded-lg"
|
className="h-8 w-8 text-muted-foreground hover:text-foreground hover:bg-muted rounded-lg transition-colors"
|
||||||
>
|
>
|
||||||
{isCollapsed ? <ChevronRight className="h-4 w-4" /> : <ChevronLeft className="h-4 w-4" />}
|
{isCollapsed ? <ChevronRight className="h-4 w-4" /> : <ChevronLeft className="h-4 w-4" />}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -84,30 +86,33 @@ const SidebarContent = () => {
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-full justify-start text-slate-700 dark:text-slate-300 hover:text-slate-900 dark:hover:text-slate-100 hover:bg-slate-100 dark:hover:bg-slate-800 rounded-lg transition-all",
|
"w-full justify-start h-10 text-muted-foreground hover:text-foreground hover:bg-muted/50 rounded-lg transition-all group",
|
||||||
pathname === item.href && "bg-blue-50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400"
|
pathname === item.href && "bg-primary/10 text-primary border border-primary/20 shadow-sm"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<item.icon className={cn(
|
<item.icon className={cn(
|
||||||
"h-5 w-5 transition-colors",
|
"h-4 w-4 transition-colors",
|
||||||
pathname === item.href ? "text-blue-600 dark:text-blue-400" : "text-slate-500 dark:text-slate-400"
|
pathname === item.href ? "text-primary" : "text-muted-foreground group-hover:text-foreground"
|
||||||
)} />
|
)} />
|
||||||
{!isCollapsed && <span className="ml-3">{item.label}</span>}
|
{!isCollapsed && <span className="ml-3 font-medium text-sm">{item.label}</span>}
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{/* Libraries Section */}
|
{/* Libraries Section */}
|
||||||
{libraries.length > 0 && (
|
{libraries.length > 0 && (
|
||||||
<div className="pt-4">
|
<div className="pt-6">
|
||||||
<h2
|
<div className={cn(
|
||||||
className={cn(
|
"flex items-center gap-2 px-3 mb-3",
|
||||||
"text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wider px-3 mb-2",
|
isCollapsed && "justify-center"
|
||||||
isCollapsed && "text-center text-[10px]"
|
)}>
|
||||||
)}
|
<FolderOpen className="h-4 w-4 text-muted-foreground" />
|
||||||
>
|
{!isCollapsed && (
|
||||||
{!isCollapsed ? "Libraries" : "Libs"}
|
<h2 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">
|
||||||
|
Libraries
|
||||||
</h2>
|
</h2>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
{libraries.map((lib) => (
|
{libraries.map((lib) => (
|
||||||
<Link
|
<Link
|
||||||
|
|
@ -118,15 +123,17 @@ const SidebarContent = () => {
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-full justify-start text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-slate-100 hover:bg-slate-100 dark:hover:bg-slate-800 rounded-lg transition-all",
|
"w-full justify-start h-9 text-muted-foreground hover:text-foreground hover:bg-muted/50 rounded-lg transition-all group text-sm",
|
||||||
pathname === "/folder-viewer" &&
|
pathname === "/folder-viewer" &&
|
||||||
searchParams.get("path") === lib.path &&
|
searchParams.get("path") === lib.path &&
|
||||||
"bg-slate-100 dark:bg-slate-800 text-slate-900 dark:text-slate-100"
|
"bg-accent text-accent-foreground border border-accent/20"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Folder className="h-4 w-4 text-slate-400 dark:text-slate-500" />
|
<Folder className="h-3.5 w-3.5 text-muted-foreground group-hover:text-foreground" />
|
||||||
{!isCollapsed && (
|
{!isCollapsed && (
|
||||||
<span className="ml-3 text-sm truncate">{lib.path.split('/').pop() || lib.path}</span>
|
<span className="ml-3 truncate text-xs">
|
||||||
|
{lib.path.split('/').pop() || lib.path}
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
@ -137,9 +144,9 @@ const SidebarContent = () => {
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<div className="p-4 border-t border-slate-200 dark:border-slate-800">
|
<div className="p-4 border-t border-border">
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
"text-center text-xs text-slate-500 dark:text-slate-400",
|
"text-center text-xs text-muted-foreground",
|
||||||
isCollapsed && "text-[10px]"
|
isCollapsed && "text-[10px]"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
@ -152,7 +159,7 @@ const SidebarContent = () => {
|
||||||
|
|
||||||
const Sidebar = () => {
|
const Sidebar = () => {
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<div className="w-20 bg-slate-50 dark:bg-slate-900 border-r border-slate-200 dark:border-slate-800" />}>
|
<Suspense fallback={<div className="w-16 bg-card border-r border-border" />}>
|
||||||
<SidebarContent />
|
<SidebarContent />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
|
|
||||||
import type { Config } from "tailwindcss"
|
import type { Config } from "tailwindcss"
|
||||||
|
|
||||||
const config = {
|
const config: Config = {
|
||||||
darkMode: "class",
|
darkMode: ["class"],
|
||||||
content: [
|
content: [
|
||||||
'./pages/**/*.{ts,tsx}',
|
'./pages/**/*.{ts,tsx}',
|
||||||
'./components/**/*.{ts,tsx}',
|
'./components/**/*.{ts,tsx}',
|
||||||
'./app/**/*.{ts,tsx}',
|
'./app/**/*.{ts,tsx}',
|
||||||
'./src/**/*.{ts,tsx}',
|
'./src/**/*.{ts,tsx}',
|
||||||
],
|
],
|
||||||
prefix: "",
|
|
||||||
theme: {
|
theme: {
|
||||||
container: {
|
container: {
|
||||||
center: true,
|
center: true,
|
||||||
|
|
@ -76,6 +75,6 @@ const config = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [require("tailwindcss-animate")],
|
plugins: [require("tailwindcss-animate")],
|
||||||
} satisfies Config
|
}
|
||||||
|
|
||||||
export default config
|
export default config
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue