@@ -185,19 +177,11 @@ export function AddShelfModal({ isOpen, onClose }: AddShelfModalProps) {
) : (
<>
diff --git a/src/components/ui/ManageShelfModal.tsx b/src/components/ui/ManageShelfModal.tsx
new file mode 100644
index 0000000..e46fa09
--- /dev/null
+++ b/src/components/ui/ManageShelfModal.tsx
@@ -0,0 +1,136 @@
+'use client';
+
+import React, { useState } from 'react';
+import { Modal } from './Modal';
+import { GenericShelf } from '@/lib/hooks/useShelves';
+import { useUpdateGoodreadsShelf } from '@/lib/hooks/useGoodreadsShelves';
+import { useUpdateHardcoverShelf } from '@/lib/hooks/useHardcoverShelves';
+import { cn } from '@/lib/utils/cn';
+
+interface ManageShelfModalProps {
+ shelf: GenericShelf | null;
+ isOpen: boolean;
+ onClose: () => void;
+}
+
+export function ManageShelfModal({ shelf, isOpen, onClose }: ManageShelfModalProps) {
+ const [rssUrl, setRssUrl] = useState(shelf?.type === 'goodreads' ? shelf.sourceId : '');
+ const [listId, setListId] = useState(shelf?.type === 'hardcover' ? shelf.sourceId : '');
+ const [apiToken, setApiToken] = useState('');
+
+ const { updateShelf: updateGoodreads, isLoading: isUpdatingGoodreads } = useUpdateGoodreadsShelf();
+ const { updateShelf: updateHardcover, isLoading: isUpdatingHardcover } = useUpdateHardcoverShelf();
+
+ // Reset form when shelf changes
+ React.useEffect(() => {
+ if (shelf) {
+ setRssUrl(shelf.type === 'goodreads' ? shelf.sourceId : '');
+ setListId(shelf.type === 'hardcover' ? shelf.sourceId : '');
+ setApiToken('');
+ }
+ }, [shelf]);
+
+ if (!shelf) return null;
+
+ const isUpdating = isUpdatingGoodreads || isUpdatingHardcover;
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ try {
+ if (shelf.type === 'goodreads') {
+ if (!rssUrl.trim()) return;
+ await updateGoodreads(shelf.id, rssUrl.trim());
+ } else {
+ if (!listId.trim()) return;
+ await updateHardcover(shelf.id, {
+ listId: listId.trim(),
+ apiToken: apiToken.trim() || undefined,
+ });
+ }
+ onClose();
+ } catch (err) {
+ // Error is handled by hook
+ }
+ };
+
+ const isGoodreads = shelf.type === 'goodreads';
+
+ return (
+
+
+
+ );
+}
diff --git a/src/lib/hooks/useGoodreadsShelves.ts b/src/lib/hooks/useGoodreadsShelves.ts
index c803663..d67477b 100644
--- a/src/lib/hooks/useGoodreadsShelves.ts
+++ b/src/lib/hooks/useGoodreadsShelves.ts
@@ -125,3 +125,53 @@ export function useDeleteGoodreadsShelf() {
return { deleteShelf, isLoading, error };
}
+
+export function useUpdateGoodreadsShelf() {
+ const { accessToken } = useAuth();
+ const [isLoading, setIsLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const updateShelf = async (shelfId: string, rssUrl: string) => {
+ if (!accessToken) throw new Error('Not authenticated');
+
+ setIsLoading(true);
+ setError(null);
+
+ try {
+ const response = await fetchWithAuth(
+ `/api/user/goodreads-shelves/${shelfId}`,
+ {
+ method: 'PATCH',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ rssUrl }),
+ },
+ );
+
+ const data = await response.json();
+
+ if (!response.ok) {
+ throw new Error(data.message || data.error || 'Failed to update shelf');
+ }
+
+ // Revalidate shelves list
+ mutate(
+ (key) =>
+ typeof key === 'string' &&
+ key.includes('/api/user/goodreads-shelves'),
+ );
+ mutate(
+ (key) => typeof key === 'string' && key.includes('/api/user/shelves'),
+ );
+
+ return data.shelf as GoodreadsShelf;
+ } catch (err) {
+ const message = err instanceof Error ? err.message : 'Unknown error';
+ setError(message);
+ throw err;
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ return { updateShelf, isLoading, error };
+}
diff --git a/src/lib/hooks/useHardcoverShelves.ts b/src/lib/hooks/useHardcoverShelves.ts
index 6dd24a6..f9a4bcc 100644
--- a/src/lib/hooks/useHardcoverShelves.ts
+++ b/src/lib/hooks/useHardcoverShelves.ts
@@ -133,3 +133,56 @@ export function useDeleteHardcoverShelf() {
return { deleteShelf, isLoading, error };
}
+
+export function useUpdateHardcoverShelf() {
+ const { accessToken } = useAuth();
+ const [isLoading, setIsLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const updateShelf = async (
+ shelfId: string,
+ updates: { listId?: string; apiToken?: string },
+ ) => {
+ if (!accessToken) throw new Error('Not authenticated');
+
+ setIsLoading(true);
+ setError(null);
+
+ try {
+ const response = await fetchWithAuth(
+ `/api/user/hardcover-shelves/${shelfId}`,
+ {
+ method: 'PATCH',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(updates),
+ },
+ );
+
+ const data = await response.json();
+
+ if (!response.ok) {
+ throw new Error(data.message || data.error || 'Failed to update list');
+ }
+
+ // Revalidate shelves list
+ mutate(
+ (key) =>
+ typeof key === 'string' &&
+ key.includes('/api/user/hardcover-shelves'),
+ );
+ mutate(
+ (key) => typeof key === 'string' && key.includes('/api/user/shelves'),
+ );
+
+ return data.shelf as HardcoverShelf;
+ } catch (err) {
+ const message = err instanceof Error ? err.message : 'Unknown error';
+ setError(message);
+ throw err;
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ return { updateShelf, isLoading, error };
+}