feat: following stuff

This commit is contained in:
2025-03-10 20:18:28 +01:00
parent 07536b8c64
commit 8d33c7eb62
6 changed files with 122 additions and 8 deletions

View File

@@ -2,6 +2,32 @@ import { validateRequest } from '@/lib/auth';
import prisma from '@/lib/db';
import { NextRequest } from 'next/server';
export async function GET(request: NextRequest) {
const { user } = await validateRequest();
const searchParams = new URL(request.url).searchParams;
const username = searchParams.get('username');
if (!user) {
return new Response('Unauthorized', { status: 401 });
}
if (!username) {
return new Response('Bad Request', { status: 400 });
}
const isFollowing =
(await prisma.follow.count({
where: {
channel: {
name: username,
},
user: {
id: user.id,
},
},
})) > 0;
return new Response(JSON.stringify({ following: isFollowing }), { status: 200 });
}
export async function POST(request: NextRequest) {
const { user } = await validateRequest();
const searchParams = new URL(request.url).searchParams;

View File

@@ -0,0 +1,32 @@
import { NextRequest, NextResponse } from 'next/server';
import db from '@/lib/db';
import { resolveChannelNameId } from '@/lib/db/resolve';
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ channel: string }> }
) {
try {
const { channel } = await params;
if (!channel) {
return NextResponse.json({ error: 'channel ID is required' }, { status: 400 });
}
const channelId = await resolveChannelNameId(channel);
const count = await db.follow.count({
where: {
channelId,
},
});
return NextResponse.json({
count,
success: true,
});
} catch (error) {
console.error('Error fetching followers count:', error);
return NextResponse.json({ error: 'Failed to fetch followers count' }, { status: 500 });
}
}

View File

@@ -1,7 +1,7 @@
import { Button } from '@/components/ui/button';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Avatar, AvatarImage } from '@/components/ui/avatar';
import type { StreamInfo, User } from '@prisma/client';
import FollowButton from './follow';
import FollowCountText from './followCount';
export default function UserInfoCard(props: Props) {
return (
@@ -14,9 +14,10 @@ export default function UserInfoCard(props: Props) {
<div>
<h1 className="text-2xl font-bold">{props.streamInfo.title}</h1>
<p>{props.streamInfo.username}</p>
<FollowCountText channel={props.streamInfo.username} />
</div>
</div>
<FollowButton channel={props.streamInfo.username} isFollowing />
<FollowButton channel={props.streamInfo.username} />
</div>
<p className="mb-4">markdown description here</p>
{/* <div className="flex items-center space-x-4 text-gray-400">

View File

@@ -4,13 +4,27 @@ import { Button } from '@/components/ui/button';
import { fetcher } from '@/lib/services/swr';
import { Heart, HeartCrack } from 'lucide-react';
import { useHover } from '@uidotdev/usehooks';
import useSWR from 'swr/mutation';
import useSWR from 'swr';
import mutatedUseSWR from 'swr/mutation';
import React from 'react';
export default function FollowButton(props: Props) {
const [ref, isHovering] = useHover();
const [following, setFollowing] = React.useState(props.isFollowing);
const { trigger, data, isMutating } = useSWR(
// const [following, setFollowing] = React.useState(props.isFollowing);
// make a get request to check if the user is following the channel and set it as the initial state. use swr to make the request
const { data: followingData, isLoading: isLoadingFollowing } = useSWR(
`/api/stream/follow?username=${props.channel}`,
async (url) => fetcher(url)
);
const [following, setFollowing] = React.useState(false);
React.useEffect(() => {
if (followingData) {
setFollowing(followingData.following);
}
}, [followingData]);
const { trigger, data, isMutating } = mutatedUseSWR(
`/api/stream/follow?username=${props.channel}`,
async (url) => fetcher(url, { method: 'POST' })
);
@@ -25,7 +39,7 @@ export default function FollowButton(props: Props) {
<Button
size={'icon'}
onClick={() => trigger()}
disabled={isMutating}
disabled={isMutating || isLoadingFollowing}
ref={ref}
variant={following ? 'destructive' : 'default'}
>
@@ -36,5 +50,4 @@ export default function FollowButton(props: Props) {
interface Props {
channel: string;
isFollowing: boolean;
}

View File

@@ -0,0 +1,26 @@
'use client';
import { fetcher } from '@/lib/services/swr';
import useSWR from 'swr';
// TODO: rerender the component when the user follows/unfollows the channel
export default function FollowCountText({ channel }: { channel: string }) {
const { data, error, isLoading } = useSWR(
`/api/stream/followers/${channel}`,
fetcher
);
if (isLoading || !data) {
return <p className="text-sm italic text-muted-foreground">Loading followers...</p>;
}
if (error) {
return <p className="text-sm italic text-muted-foreground">Failed to load followers</p>;
}
return (
<p className="text-sm text-muted-foreground">
{data.count} follower{data.count === 1 ? '' : 's'}
</p>
);
}

16
src/lib/db/resolve.ts Normal file
View File

@@ -0,0 +1,16 @@
'use server'
import db from '@/lib/db';
export async function resolveChannelNameId(channelName: string) {
const channel = await db.channel.findUnique({
where: {
name: channelName,
},
});
if (!channel) {
return '';
}
return channel.id;
}