All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 41s
146 lines
4.0 KiB
TypeScript
146 lines
4.0 KiB
TypeScript
import {
|
|
type ReactNode,
|
|
useCallback,
|
|
useEffect,
|
|
useMemo,
|
|
useState,
|
|
} from "react";
|
|
import { FollowContext, type FollowContextValue } from "./FollowContext.ts";
|
|
import { API_URL } from "../config/api.ts";
|
|
import { useAuth } from "../hooks/useAuth.ts";
|
|
import type { FollowStatus } from "../model.ts";
|
|
|
|
export function FollowProvider({ children }: { children: ReactNode }) {
|
|
const { token, authFetch } = useAuth();
|
|
const [followedUserIds, setFollowedUserIds] = useState<Set<string>>(
|
|
new Set(),
|
|
);
|
|
const [followedPlaylistIds, setFollowedPlaylistIds] = useState<Set<string>>(
|
|
new Set(),
|
|
);
|
|
const [isLoaded, setIsLoaded] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (!token) {
|
|
// Deferred to a microtask (rather than called directly) so this
|
|
// doesn't fire synchronously within the effect body — same timing
|
|
// for the user, just not in the same tick.
|
|
queueMicrotask(() => {
|
|
setFollowedUserIds(new Set());
|
|
setFollowedPlaylistIds(new Set());
|
|
setIsLoaded(false);
|
|
});
|
|
return;
|
|
}
|
|
const controller = new AbortController();
|
|
fetch(`${API_URL}/api/follows/status`, {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
signal: controller.signal,
|
|
})
|
|
.then((r) => r.json())
|
|
.then((body) => {
|
|
if (!body.success) return;
|
|
const status = body.data as FollowStatus;
|
|
setFollowedUserIds(new Set(status.followedUserIds));
|
|
setFollowedPlaylistIds(new Set(status.followedPlaylistIds));
|
|
setIsLoaded(true);
|
|
})
|
|
.catch((err) => {
|
|
if (err.name !== "AbortError") setIsLoaded(true);
|
|
});
|
|
return () => {
|
|
controller.abort();
|
|
};
|
|
}, [token]);
|
|
|
|
const followUser = useCallback(async (userId: string) => {
|
|
setFollowedUserIds((prev) => new Set([...prev, userId]));
|
|
try {
|
|
const res = await authFetch(`${API_URL}/api/follows/users/${userId}`, {
|
|
method: "POST",
|
|
});
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
} catch {
|
|
setFollowedUserIds((prev) => {
|
|
const n = new Set(prev);
|
|
n.delete(userId);
|
|
return n;
|
|
});
|
|
}
|
|
}, [authFetch]);
|
|
|
|
const unfollowUser = useCallback(async (userId: string) => {
|
|
setFollowedUserIds((prev) => {
|
|
const n = new Set(prev);
|
|
n.delete(userId);
|
|
return n;
|
|
});
|
|
try {
|
|
const res = await authFetch(`${API_URL}/api/follows/users/${userId}`, {
|
|
method: "DELETE",
|
|
});
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
} catch {
|
|
setFollowedUserIds((prev) => new Set([...prev, userId]));
|
|
}
|
|
}, [authFetch]);
|
|
|
|
const followPlaylist = useCallback(async (playlistId: string) => {
|
|
setFollowedPlaylistIds((prev) => new Set([...prev, playlistId]));
|
|
try {
|
|
const res = await authFetch(
|
|
`${API_URL}/api/follows/playlists/${playlistId}`,
|
|
{ method: "POST" },
|
|
);
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
} catch {
|
|
setFollowedPlaylistIds((prev) => {
|
|
const n = new Set(prev);
|
|
n.delete(playlistId);
|
|
return n;
|
|
});
|
|
}
|
|
}, [authFetch]);
|
|
|
|
const unfollowPlaylist = useCallback(async (playlistId: string) => {
|
|
setFollowedPlaylistIds((prev) => {
|
|
const n = new Set(prev);
|
|
n.delete(playlistId);
|
|
return n;
|
|
});
|
|
try {
|
|
const res = await authFetch(
|
|
`${API_URL}/api/follows/playlists/${playlistId}`,
|
|
{ method: "DELETE" },
|
|
);
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
} catch {
|
|
setFollowedPlaylistIds((prev) => new Set([...prev, playlistId]));
|
|
}
|
|
}, [authFetch]);
|
|
|
|
const value: FollowContextValue = useMemo(() => ({
|
|
followedUserIds,
|
|
followedPlaylistIds,
|
|
followUser,
|
|
unfollowUser,
|
|
followPlaylist,
|
|
unfollowPlaylist,
|
|
isLoaded,
|
|
}), [
|
|
followedUserIds,
|
|
followedPlaylistIds,
|
|
followUser,
|
|
unfollowUser,
|
|
followPlaylist,
|
|
unfollowPlaylist,
|
|
isLoaded,
|
|
]);
|
|
|
|
return (
|
|
<FollowContext.Provider value={value}>
|
|
{children}
|
|
</FollowContext.Provider>
|
|
);
|
|
}
|