feat(detail): like/dislike buttons on the manga page
Add a tri-state like/dislike toggle beside the bookmark action (signed-in only): click a reaction to set it, the other to switch, or the active one to clear — optimistic with rollback, mirroring the bookmark toggle. Seeds from the new GET /me/reactions/:id in the detail loader (non-critical: any failure degrades to an unset toggle). Unit tests cover the state machine; e2e drives set → switch → clear against the real endpoints. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
42
frontend/src/lib/api/reactions.ts
Normal file
42
frontend/src/lib/api/reactions.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { ApiError, request } from './client';
|
||||
|
||||
/** A private per-user taste signal on a manga. */
|
||||
export type Reaction = 'like' | 'dislike';
|
||||
|
||||
export type MangaReaction = {
|
||||
manga_id: string;
|
||||
reaction: Reaction | null;
|
||||
};
|
||||
|
||||
/** PUT /v1/mangas/:id/reaction — set (or switch) the user's reaction. */
|
||||
export async function setReaction(mangaId: string, reaction: Reaction): Promise<MangaReaction> {
|
||||
return request<MangaReaction>(`/v1/mangas/${encodeURIComponent(mangaId)}/reaction`, {
|
||||
method: 'PUT',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ reaction })
|
||||
});
|
||||
}
|
||||
|
||||
/** DELETE /v1/mangas/:id/reaction — clear the user's reaction (idempotent). */
|
||||
export async function clearReaction(mangaId: string): Promise<void> {
|
||||
await request<void>(`/v1/mangas/${encodeURIComponent(mangaId)}/reaction`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user's reaction for a manga, or `null` when they haven't
|
||||
* reacted (or aren't signed in). Used by the detail page to seed the
|
||||
* like/dislike toggle.
|
||||
*/
|
||||
export async function getMyReactionForManga(mangaId: string): Promise<Reaction | null> {
|
||||
try {
|
||||
const r = await request<MangaReaction>(
|
||||
`/v1/me/reactions/${encodeURIComponent(mangaId)}`
|
||||
);
|
||||
return r.reaction;
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError && (e.status === 401 || e.status === 404)) return null;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user