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>
43 lines
1.4 KiB
TypeScript
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;
|
|
}
|
|
}
|