feat: implement add media to album functionality with dialog and API integration

This commit is contained in:
2025-11-16 01:38:04 +01:00
parent 43157cef4e
commit 07b797b82b
10 changed files with 321 additions and 57 deletions

View File

@@ -0,0 +1,144 @@
import { useState } from "react";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
DialogFooter,
DialogClose,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { useGetMediaList } from "@/features/media/use-media";
import { useAddMediaToAlbum } from "@/features/albums/use-albums";
import { AuthenticatedImage } from "@/components/media/authenticated-image";
import { Plus } from "lucide-react";
import { cn } from "@/lib/utils";
type AddMediaToAlbumDialogProps = {
albumId: string;
};
export function AddMediaToAlbumDialog({ albumId }: AddMediaToAlbumDialogProps) {
const [isOpen, setIsOpen] = useState(false);
const [selectedMediaIds, setSelectedMediaIds] = useState<string[]>([]);
const {
data,
isLoading,
error,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useGetMediaList();
const { mutate: addMedia, isPending: isAdding } = useAddMediaToAlbum();
const toggleSelection = (mediaId: string) => {
setSelectedMediaIds((prev) =>
prev.includes(mediaId)
? prev.filter((id) => id !== mediaId)
: [...prev, mediaId]
);
};
const handleSubmit = () => {
addMedia(
{
albumId,
payload: { media_ids: selectedMediaIds },
},
{
onSuccess: () => {
setIsOpen(false);
setSelectedMediaIds([]);
},
}
);
};
return (
<Dialog
open={isOpen}
onOpenChange={(open) => {
setIsOpen(open);
if (!open) {
setSelectedMediaIds([]);
}
}}
>
<DialogTrigger asChild>
<Button variant="outline">
<Plus size={18} className="mr-2" />
Add Photos
</Button>
</DialogTrigger>
<DialogContent className="max-w-5xl h-[80vh] flex flex-col">
<DialogHeader>
<DialogTitle>Add Photos to Album</DialogTitle>
<DialogDescription>
Select photos from your library to add to this album.
</DialogDescription>
</DialogHeader>
<div className="flex-1 overflow-y-auto pr-2">
{isLoading && <p>Loading photos...</p>}
{error && <p>Error loading photos: {error.message}</p>}
{data && (
<div className="grid grid-cols-4 sm:grid-cols-5 md:grid-cols-6 lg:grid-cols-8 gap-2">
{data.pages.map((page) =>
page.data.map((media) => {
const isSelected = selectedMediaIds.includes(media.id);
return (
<div
key={media.id}
className={cn(
"aspect-square bg-gray-200 rounded-md overflow-hidden cursor-pointer transition-all",
isSelected
? "ring-4 ring-blue-500 ring-offset-2 opacity-100"
: "opacity-80 hover:opacity-100"
)}
onClick={() => toggleSelection(media.id)}
>
<AuthenticatedImage
src={media.thumbnail_url ?? media.file_url}
alt={media.original_filename}
className="w-full h-full object-cover"
/>
</div>
);
})
)}
</div>
)}
{hasNextPage && (
<div className="flex justify-center mt-6">
<Button
onClick={() => fetchNextPage()}
disabled={isFetchingNextPage}
>
{isFetchingNextPage ? "Loading more..." : "Load More"}
</Button>
</div>
)}
</div>
<DialogFooter>
<DialogClose asChild>
<Button type="button" variant="outline">
Cancel
</Button>
</DialogClose>
<Button
type="submit"
onClick={handleSubmit}
disabled={selectedMediaIds.length === 0 || isAdding}
>
{isAdding ? "Adding..." : `Add ${selectedMediaIds.length} Photo(s)`}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View File

@@ -28,7 +28,6 @@ export function AlbumCard({ album }: AlbumCardProps) {
</CardContent>
<CardFooter className="p-4 pt-0">
{/* TODO: Show photo count */}
<p className="text-sm text-gray-600">0 items</p>
</CardFooter>
</Card>
</Link>