mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-03 12:50:09 +00:00
20c8fb0898
Introduce user-reported-issues and Goodreads shelf sync features and wire them into notifications. Adds Prisma migrations and schema changes (ReportedIssue, GoodreadsShelf, GoodreadsBookMapping), API endpoints for reporting (POST /audiobooks/[asin]/report-issue) and admin management (list, resolve/dismiss, replace), and an admin UI section to view/dismiss/replace reported issues. Adds a new notification event (issue_reported) with updates to notification schemas, docs and provider handling, plus a notification-events constants file. Refactors request creation to use createRequestForUser service, adds a Goodreads sync processor/service/hooks/UI modals, a scrape-resilience util, and related tests and minor integration updates.
128 lines
3.1 KiB
TypeScript
128 lines
3.1 KiB
TypeScript
/**
|
|
* Component: Goodreads Shelves Hook
|
|
* Documentation: documentation/frontend/components.md
|
|
*/
|
|
|
|
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import useSWR, { mutate } from 'swr';
|
|
import { useAuth } from '@/contexts/AuthContext';
|
|
import { fetchWithAuth } from '@/lib/utils/api';
|
|
|
|
export interface ShelfBook {
|
|
coverUrl: string;
|
|
asin: string | null;
|
|
title: string;
|
|
author: string;
|
|
}
|
|
|
|
export interface GoodreadsShelf {
|
|
id: string;
|
|
name: string;
|
|
rssUrl: string;
|
|
lastSyncAt: string | null;
|
|
createdAt: string;
|
|
bookCount: number | null;
|
|
books: ShelfBook[];
|
|
}
|
|
|
|
const fetcher = (url: string) =>
|
|
fetchWithAuth(url).then((res) => res.json());
|
|
|
|
export function useGoodreadsShelves() {
|
|
const { accessToken } = useAuth();
|
|
|
|
const endpoint = accessToken ? '/api/user/goodreads-shelves' : null;
|
|
|
|
const { data, error, isLoading } = useSWR(
|
|
endpoint,
|
|
fetcher,
|
|
{ refreshInterval: 30000 }
|
|
);
|
|
|
|
return {
|
|
shelves: (data?.shelves || []) as GoodreadsShelf[],
|
|
isLoading,
|
|
error,
|
|
};
|
|
}
|
|
|
|
export function useAddGoodreadsShelf() {
|
|
const { accessToken } = useAuth();
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const addShelf = async (rssUrl: string) => {
|
|
if (!accessToken) throw new Error('Not authenticated');
|
|
|
|
setIsLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
const response = await fetchWithAuth('/api/user/goodreads-shelves', {
|
|
method: 'POST',
|
|
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 add shelf');
|
|
}
|
|
|
|
// Revalidate shelves list
|
|
mutate((key) => typeof key === 'string' && key.includes('/api/user/goodreads-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 { addShelf, isLoading, error };
|
|
}
|
|
|
|
export function useDeleteGoodreadsShelf() {
|
|
const { accessToken } = useAuth();
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const deleteShelf = async (shelfId: string) => {
|
|
if (!accessToken) throw new Error('Not authenticated');
|
|
|
|
setIsLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
const response = await fetchWithAuth(`/api/user/goodreads-shelves/${shelfId}`, {
|
|
method: 'DELETE',
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (!response.ok) {
|
|
throw new Error(data.message || data.error || 'Failed to remove shelf');
|
|
}
|
|
|
|
// Revalidate shelves list
|
|
mutate((key) => typeof key === 'string' && key.includes('/api/user/goodreads-shelves'));
|
|
|
|
return true;
|
|
} catch (err) {
|
|
const message = err instanceof Error ? err.message : 'Unknown error';
|
|
setError(message);
|
|
throw err;
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
return { deleteShelf, isLoading, error };
|
|
}
|