Files
k-photos/k-photos-frontend/components/album-sidebar.tsx
Gabriel Kaszewski 957737ac9b feat: frontend MVP — auth, timeline, upload, albums, admin, image viewer
Backend:
- user roles (DB + JWT + first-user-is-admin)
- volume-aware file resolver (multi-volume asset serving)
- directory scanner uses volume URI directly
- date-summary endpoint (capture date from EXIF)
- timeline ordered by capture date
- list endpoints: volumes, plugins, pipelines, library paths
- delete endpoints: volumes, library paths
- configurable upload body limit (MAX_UPLOAD_BYTES)

Frontend:
- auth: login/register, token refresh, role-based admin gate
- timeline: date-grouped grid, infinite scroll, date scrubber
- image viewer: fullscreen zoom/pan/pinch, metadata sidebar
- upload: drag-drop, sequential upload, progress tracking
- albums: create, add/remove photos, asset picker dialog
- admin: storage (import library), jobs (pagination, error details),
  plugins (list + toggle), pipelines, sidecars, duplicates
- multi-select mode with add-to-album action
- TanStack Query for all data fetching
2026-06-01 01:35:43 +02:00

79 lines
2.2 KiB
TypeScript

"use client"
import { useState } from "react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import {
SidebarGroup,
SidebarGroupLabel,
SidebarGroupContent,
SidebarMenu,
SidebarMenuItem,
SidebarMenuButton,
} from "@/components/ui/sidebar"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { useAlbums } from "@/hooks/use-albums"
import { ImageIcon, PlusIcon } from "lucide-react"
export function AlbumSidebar() {
const pathname = usePathname()
const { albums, createAlbum } = useAlbums()
const [isCreating, setIsCreating] = useState(false)
const [newTitle, setNewTitle] = useState("")
const handleCreate = async () => {
if (!newTitle.trim()) return
await createAlbum(newTitle.trim()).catch(() => {})
setNewTitle("")
setIsCreating(false)
}
return (
<SidebarGroup>
<SidebarGroupLabel className="flex items-center justify-between">
Albums
<Button
variant="ghost"
size="icon"
className="h-5 w-5"
onClick={() => setIsCreating(!isCreating)}
>
<PlusIcon className="h-3.5 w-3.5" />
</Button>
</SidebarGroupLabel>
<SidebarGroupContent>
{isCreating && (
<div className="px-2 pb-2">
<Input
autoFocus
placeholder="Album title"
value={newTitle}
onChange={(e) => setNewTitle(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") handleCreate()
if (e.key === "Escape") setIsCreating(false)
}}
/>
</div>
)}
<SidebarMenu>
{albums.map((album) => (
<SidebarMenuItem key={album.id}>
<SidebarMenuButton
asChild
isActive={pathname === `/albums/${album.id}`}
>
<Link href={`/albums/${album.id}`}>
<ImageIcon className="h-4 w-4" />
<span>{album.title}</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
)
}