- volume-aware deletion: read-only volumes remove DB only, writable volumes soft-delete to trash with configurable grace period - trash page with restore, worker purge sweep (TRASH_RETENTION_DAYS) - album delete endpoint + sidebar trash icon - asset delete from timeline selection toolbar - all listing queries exclude trashed assets (deleted_at IS NULL) - timeline ordered by EXIF capture date, date-summary endpoint - README rewritten with features, setup, full env var table
86 lines
2.6 KiB
TypeScript
86 lines
2.6 KiB
TypeScript
"use client"
|
|
|
|
import { useEffect } from "react"
|
|
import { useRouter } from "next/navigation"
|
|
import { useAuth } from "@/hooks/use-auth"
|
|
import {
|
|
SidebarProvider,
|
|
Sidebar,
|
|
SidebarContent,
|
|
SidebarHeader,
|
|
SidebarInset,
|
|
SidebarTrigger,
|
|
} from "@/components/ui/sidebar"
|
|
import { Separator } from "@/components/ui/separator"
|
|
import { AlbumSidebar } from "@/components/album-sidebar"
|
|
import { AdminSidebar } from "@/components/admin-sidebar"
|
|
import { UploadDialog } from "@/components/upload-dialog"
|
|
import { Spinner } from "@/components/ui/spinner"
|
|
import { CameraIcon, LogOutIcon, Trash2Icon } from "lucide-react"
|
|
import { Button } from "@/components/ui/button"
|
|
import Link from "next/link"
|
|
|
|
export default function AppLayout({ children }: { children: React.ReactNode }) {
|
|
const { user, isAuthenticated, isLoading, logout } = useAuth()
|
|
const router = useRouter()
|
|
|
|
useEffect(() => {
|
|
if (!isLoading && !isAuthenticated) {
|
|
router.replace("/login")
|
|
}
|
|
}, [isLoading, isAuthenticated, router])
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="flex min-h-svh items-center justify-center">
|
|
<Spinner />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (!isAuthenticated) return null
|
|
|
|
return (
|
|
<SidebarProvider>
|
|
<Sidebar>
|
|
<SidebarHeader className="flex flex-row items-center gap-2 px-4 py-3">
|
|
<Link href="/" className="flex items-center gap-2 font-semibold">
|
|
<CameraIcon className="h-5 w-5" />
|
|
K-Photos
|
|
</Link>
|
|
</SidebarHeader>
|
|
<SidebarContent>
|
|
<AlbumSidebar />
|
|
<AdminSidebar />
|
|
</SidebarContent>
|
|
<div className="flex flex-col border-t">
|
|
<Link
|
|
href="/trash"
|
|
className="flex items-center gap-2 px-4 py-2 text-xs text-muted-foreground hover:text-foreground"
|
|
>
|
|
<Trash2Icon className="h-3.5 w-3.5" />
|
|
Trash
|
|
</Link>
|
|
<div className="flex items-center justify-between px-4 py-2">
|
|
<span className="truncate text-xs text-muted-foreground">
|
|
{user?.username}
|
|
</span>
|
|
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={logout}>
|
|
<LogOutIcon className="h-3.5 w-3.5" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</Sidebar>
|
|
<SidebarInset>
|
|
<header className="flex h-12 items-center gap-2 border-b px-4">
|
|
<SidebarTrigger />
|
|
<Separator orientation="vertical" className="h-4" />
|
|
<div className="flex-1" />
|
|
<UploadDialog />
|
|
</header>
|
|
<main className="flex-1 p-4">{children}</main>
|
|
</SidebarInset>
|
|
</SidebarProvider>
|
|
)
|
|
}
|