Files
Mangalord/frontend/src/lib/api/reactions.ts
MechaCat02 bb833c7e71 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>
2026-07-05 17:20:24 +02:00

43 lines
1.4 KiB
TypeScript

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;
}
}