jefflix-website/app/offline/page.tsx

172 lines
6.1 KiB
TypeScript

'use client'
import { useState } from 'react'
import { Button } from '@/components/ui/button'
import { JefflixLogo } from '@/components/jefflix-logo'
import { SongRow } from '@/components/music/search-results'
import { useMusicPlayer } from '@/components/music/music-provider'
import { useOffline } from '@/lib/stores/offline'
import {
ArrowLeft,
Download,
HardDrive,
Loader2,
Play,
RefreshCw,
Trash2,
WifiOff,
} from 'lucide-react'
import Link from 'next/link'
function formatBytes(bytes: number) {
if (bytes === 0) return '0 B'
const k = 1024
const sizes = ['B', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}`
}
export default function OfflinePage() {
const { state, playTrack } = useMusicPlayer()
const {
offlineTracks,
queue,
activeDownloadId,
storageUsed,
clearAll,
sync,
loading,
} = useOffline()
const [syncing, setSyncing] = useState(false)
const [clearing, setClearing] = useState(false)
const hasPlayer = !!state.currentTrack
const handleSync = async () => {
setSyncing(true)
await sync()
setSyncing(false)
}
const handleClearAll = async () => {
if (!confirm('Remove all downloaded songs? They can be re-downloaded later.')) return
setClearing(true)
await clearAll()
setClearing(false)
}
const playAllOffline = () => {
if (offlineTracks.length > 0) {
playTrack(offlineTracks[0], offlineTracks, 0)
}
}
return (
<div className={`min-h-screen bg-background ${hasPlayer ? 'pb-20' : ''}`}>
{/* Header */}
<div className="border-b border-border">
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
<Link href="/" className="inline-block">
<JefflixLogo size="small" />
</Link>
<Link href="/music">
<Button variant="ghost" size="sm">
<ArrowLeft className="h-4 w-4 mr-1.5" />
Music
</Button>
</Link>
</div>
</div>
<div className="container mx-auto px-4 py-12 md:py-16">
<div className="max-w-2xl mx-auto">
{/* Hero */}
<div className="text-center space-y-4 mb-8">
<div className="inline-block p-4 bg-blue-100 dark:bg-blue-900/30 rounded-full">
<WifiOff className="h-10 w-10 text-blue-600 dark:text-blue-400" />
</div>
<h1 className="text-3xl font-bold font-marker">Offline Library</h1>
<p className="text-muted-foreground">
Songs downloaded for offline playback. Syncs across all your devices.
</p>
</div>
{/* Stats + Actions */}
<div className="flex flex-wrap items-center gap-3 mb-6">
<div className="flex items-center gap-2 text-sm text-muted-foreground bg-muted/50 px-3 py-1.5 rounded-lg">
<HardDrive className="h-4 w-4" />
{formatBytes(storageUsed)} used
</div>
<div className="flex items-center gap-2 text-sm text-muted-foreground bg-muted/50 px-3 py-1.5 rounded-lg">
<Download className="h-4 w-4" />
{offlineTracks.length} songs
</div>
<div className="flex-1" />
<Button variant="outline" size="sm" onClick={handleSync} disabled={syncing}>
{syncing ? <Loader2 className="h-4 w-4 animate-spin mr-1.5" /> : <RefreshCw className="h-4 w-4 mr-1.5" />}
Sync
</Button>
{offlineTracks.length > 0 && (
<>
<Button size="sm" onClick={playAllOffline}>
<Play className="h-4 w-4 mr-1.5" />
Play All
</Button>
<Button variant="destructive" size="sm" onClick={handleClearAll} disabled={clearing}>
{clearing ? <Loader2 className="h-4 w-4 animate-spin mr-1.5" /> : <Trash2 className="h-4 w-4 mr-1.5" />}
Clear All
</Button>
</>
)}
</div>
{/* Download queue */}
{queue.length > 0 && (
<div className="mb-6 p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg border border-blue-200 dark:border-blue-800">
<h3 className="text-sm font-semibold mb-2 flex items-center gap-2">
<Loader2 className="h-4 w-4 animate-spin text-blue-600" />
Downloading ({queue.length} remaining)
</h3>
<div className="space-y-1 text-sm text-muted-foreground">
{queue.slice(0, 5).map((t) => (
<div key={t.id} className="flex items-center gap-2">
{t.id === activeDownloadId && <Loader2 className="h-3 w-3 animate-spin" />}
<span className="truncate">{t.title} {t.artist}</span>
</div>
))}
{queue.length > 5 && (
<div className="text-xs">...and {queue.length - 5} more</div>
)}
</div>
</div>
)}
{/* Songs list */}
{loading ? (
<div className="flex justify-center py-12">
<Loader2 className="h-6 w-6 animate-spin text-blue-600" />
</div>
) : offlineTracks.length === 0 ? (
<div className="text-center py-12 space-y-4">
<WifiOff className="h-12 w-12 mx-auto text-muted-foreground/30" />
<p className="text-muted-foreground">
No songs downloaded yet. Tap the download icon on any song to save it for offline.
</p>
<Link href="/music">
<Button variant="outline">
Browse Music
</Button>
</Link>
</div>
) : (
<div className="border border-border rounded-lg divide-y divide-border overflow-hidden">
{offlineTracks.map((song, i) => (
<SongRow key={song.id} song={song} songs={offlineTracks} index={i} showDownload />
))}
</div>
)}
</div>
</div>
</div>
)
}