4wswt4ex2: Add like and unlike for post cards

This commit is contained in:
2026-10-19 14:16:52 -06:00
parent 20cca38619
commit 28037a2849
9 changed files with 402 additions and 5 deletions
+52
View File
@@ -0,0 +1,52 @@
// Browser-side like / unlike calls and the pure card update they drive.
import type { CardPost } from "./feed-model";
export interface LikeOutcome {
isLiked: boolean;
likesCount: number | null;
}
export class LikeError extends Error {
constructor(public readonly signedOut: boolean) {
super(signedOut ? "sign_in" : "like_failed");
}
}
export async function sendLike(
postId: string,
liked: boolean,
fetchImpl: typeof fetch = fetch,
): Promise<LikeOutcome> {
let res: Response;
try {
res = await fetchImpl(`/api/posts/${encodeURIComponent(postId)}/like`, {
method: liked ? "POST" : "DELETE",
credentials: "same-origin",
});
} catch {
throw new LikeError(false);
}
if (res.status === 401) throw new LikeError(true);
if (!res.ok) throw new LikeError(false);
try {
const b = (await res.json()) as { isLiked?: unknown; likesCount?: unknown };
return {
isLiked: typeof b.isLiked === "boolean" ? b.isLiked : liked,
likesCount:
typeof b.likesCount === "number" && Number.isInteger(b.likesCount) && b.likesCount >= 0
? b.likesCount
: null,
};
} catch {
throw new LikeError(false);
}
}
/** The card after a successful call: the server count when present, else flip and adjust by one. */
export function applyLikeOutcome(post: CardPost, wanted: boolean, out: LikeOutcome): CardPost {
if (out.likesCount !== null) return { ...post, isLiked: out.isLiked, likesCount: out.likesCount };
const changed = post.isLiked !== wanted;
const delta = changed ? (wanted ? 1 : -1) : 0;
return { ...post, isLiked: wanted, likesCount: Math.max(0, post.likesCount + delta) };
}