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

@@ -0,0 +1,65 @@
import { useAuth } from "../hooks/useAuth.ts";
import { useFollows } from "../hooks/useFollows.ts";
interface FollowUserButtonProps {
targetUserId: string;
targetUsername: string;
}
interface FollowPlaylistButtonProps {
targetPlaylistId: string;
isPublic: boolean;
}
export function FollowUserButton(
{ targetUserId, targetUsername }: FollowUserButtonProps,
) {
const { user } = useAuth();
const { followedUserIds, followUser, unfollowUser, isLoaded } = useFollows();
if (!user || user.id === targetUserId) return null;
const isFollowing = followedUserIds.has(targetUserId);
return (
<button
type="button"
className={`follow-btn${isFollowing ? " follow-btn--following" : ""}`}
disabled={!isLoaded}
onClick={() =>
isFollowing ? unfollowUser(targetUserId) : followUser(targetUserId)}
aria-label={isFollowing
? `Unfollow ${targetUsername}`
: `Follow ${targetUsername}`}
>
{isFollowing ? "Following" : "Follow"}
</button>
);
}
export function FollowPlaylistButton(
{ targetPlaylistId, isPublic }: FollowPlaylistButtonProps,
) {
const { user } = useAuth();
const { followedPlaylistIds, followPlaylist, unfollowPlaylist, isLoaded } =
useFollows();
if (!user || !isPublic) return null;
const isFollowing = followedPlaylistIds.has(targetPlaylistId);
return (
<button
type="button"
className={`follow-btn${isFollowing ? " follow-btn--following" : ""}`}
disabled={!isLoaded}
onClick={() =>
isFollowing
? unfollowPlaylist(targetPlaylistId)
: followPlaylist(targetPlaylistId)}
aria-label={isFollowing ? "Unfollow playlist" : "Follow playlist"}
>
{isFollowing ? "Following" : "Follow"}
</button>
);
}