main pages

This commit is contained in:
2026-03-30 13:40:42 +03:00
parent 3be784d675
commit 430981cbf7
19 changed files with 6531 additions and 0 deletions
+99
View File
@@ -0,0 +1,99 @@
import { useNavigate } from 'react-router-dom'
import { motion } from 'framer-motion'
import { ListMusic } from '../lib/icons'
import { useLibraryItems } from '../hooks/use-jellyfin'
import PosterCard from '../components/ui/PosterCard'
import { usePosterGridClasses } from '../lib/density'
export default function PlaylistsPage() {
const navigate = useNavigate()
const gridCls = usePosterGridClasses()
const { data, isLoading } = useLibraryItems(undefined, {
includeItemTypes: ['Playlist'],
sortBy: ['SortName'],
sortOrder: ['Ascending'],
limit: 200,
})
const items = data?.Items || []
return (
<div className="px-7 pt-4 pb-12">
<div className="mb-7">
<div className="flex items-center gap-2 mb-1.5">
<span className="w-1 h-3.5 rounded-full bg-accent" />
<span className="text-[11px] font-semibold text-text-2 uppercase tracking-[0.14em]">Library</span>
</div>
<div className="flex items-baseline gap-3">
<h1 className="text-3xl md:text-4xl font-bold font-display text-text-1 tracking-tight">
Playlists
</h1>
{items.length > 0 && (
<span className="text-[12px] text-text-4 tabular-nums">
{items.length.toLocaleString()} {items.length === 1 ? 'playlist' : 'playlists'}
</span>
)}
</div>
</div>
{isLoading ? (
<SkeletonGrid />
) : items.length === 0 ? (
<EmptyState />
) : (
<div className={gridCls}>
{items.map((item, i) => (
<motion.div
key={item.Id}
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, delay: Math.min(i * 0.012, 0.3), ease: [0.16, 1, 0.3, 1] }}
>
<PosterCard
item={item}
aspect="square"
priority={i < 12}
onClick={() => item.Id && navigate(`/item/${item.Id}`)}
/>
</motion.div>
))}
</div>
)}
</div>
)
}
function EmptyState() {
return (
<div className="flex flex-col items-center justify-center min-h-[50vh] text-center">
<div className="relative w-16 h-16 mb-4">
<div className="absolute inset-0 rounded-full bg-accent/10 blur-xl" />
<div className="relative w-full h-full rounded-full bg-gradient-to-br from-elevated to-surface ring-1 ring-border grid place-items-center">
<ListMusic size={22} className="text-text-3" />
</div>
</div>
<p className="text-[15px] font-medium text-text-1 mb-1.5">No playlists yet</p>
<p className="text-[12px] text-text-4 max-w-sm">
Create a playlist on your Jellyfin server and it'll show up here.
</p>
</div>
)
}
function SkeletonGrid() {
const gridCls = usePosterGridClasses()
return (
<div className={gridCls}>
{Array.from({ length: 12 }).map((_, i) => (
<div key={i}>
<div className="skeleton aspect-square rounded-lg" />
<div className="mt-2 space-y-1">
<div className="skeleton h-3 w-3/4 rounded" />
<div className="skeleton h-2.5 w-1/2 rounded" />
</div>
</div>
))}
</div>
)
}