feat: implement add media to album functionality with dialog and API integration
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user