v3: follows, notifications, invite-only registration, unread markers

This commit is contained in:
khannurien
2026-03-21 18:42:47 +00:00
parent 7c098e7c4c
commit 608c6bc6a8
55 changed files with 4743 additions and 884 deletions

View File

@@ -16,14 +16,17 @@ import {
import { WS_URL } from "../config/api.ts";
import type {
Dump,
Notification,
OnlineUser,
RawComment,
RawDump,
RawNotification,
RawPlaylist,
} from "../model.ts";
import {
deserializeComment,
deserializeDump,
deserializeNotification,
deserializePlaylist,
} from "../model.ts";
@@ -52,6 +55,10 @@ export function WSProvider({ children, token }: WSProviderProps) {
const [lastCommentEvent, setLastCommentEvent] = useState<CommentEvent | null>(
null,
);
const [unreadNotificationCount, setUnreadNotificationCount] = useState(0);
const [lastNotification, setLastNotification] = useState<Notification | null>(
null,
);
// Refs to avoid stale closures in event handlers
const voteCountsRef = useRef(voteCounts);
@@ -100,6 +107,9 @@ export function WSProvider({ children, token }: WSProviderProps) {
const votes = msg.myVotes as string[];
setOnlineUsers(users);
setMyVotes(new Set(votes));
setUnreadNotificationCount(
(msg.unreadNotificationCount as number) ?? 0,
);
break;
}
@@ -217,6 +227,15 @@ export function WSProvider({ children, token }: WSProviderProps) {
break;
}
case "notification_created": {
const notification = deserializeNotification(
msg.notification as RawNotification,
);
setLastNotification(notification);
setUnreadNotificationCount((prev) => prev + 1);
break;
}
case "error":
// On error, revert any pending optimistic update for the affected dump
// (the revert timeout will handle it)
@@ -309,6 +328,17 @@ export function WSProvider({ children, token }: WSProviderProps) {
socketRef.current?.send(JSON.stringify({ type: "vote_remove", dumpId }));
}, []);
const injectDump = useCallback((dump: Dump) => {
setRecentDumps((prev) => {
if (prev.some((d) => d.id === dump.id)) return prev;
return [dump, ...prev];
});
}, []);
const clearUnreadNotifications = useCallback(() => {
setUnreadNotificationCount(0);
}, []);
const value: WSContextValue = {
onlineUsers,
voteCounts,
@@ -320,8 +350,12 @@ export function WSProvider({ children, token }: WSProviderProps) {
lastPlaylistEvent,
deletedPlaylistIds,
lastCommentEvent,
unreadNotificationCount,
lastNotification,
castVote,
removeVote,
injectDump,
clearUnreadNotifications,
};
return (