diff --git a/src/app/(user)/projects/[id]/page.tsx b/src/app/(user)/projects/[id]/page.tsx index e577f90..b2715e8 100644 --- a/src/app/(user)/projects/[id]/page.tsx +++ b/src/app/(user)/projects/[id]/page.tsx @@ -434,7 +434,7 @@ export default async function ProjectPage({ params }: ProjectPageProps) { {project.contributors.length > 0 && (
- + {project.contributors.length} diff --git a/src/app/api/projects/route.ts b/src/app/api/projects/route.ts index 76f839a..f555d18 100644 --- a/src/app/api/projects/route.ts +++ b/src/app/api/projects/route.ts @@ -72,6 +72,25 @@ export async function GET(req: Request) { ]; } + const session = await getServerSession(authOptions); + const userId = session?.user?.id; + + let userLikes: string[] = []; + if (userId) { + const userProfile = await db.profile.findUnique({ + where: { userId }, + select: { id: true } + }); + + if (userProfile) { + const likes = await db.like.findMany({ + where: { profileId: userProfile.id }, + select: { projectId: true } + }); + userLikes = likes.map(like => like.projectId); + } + } + const projects = await db.project.findMany({ skip: (page - 1) * limit, take: limit, @@ -103,8 +122,14 @@ export async function GET(req: Request) { }, }); + const projectsWithLikes = projects.map(project => ({ + ...project, + isLiked: userLikes.includes(project.id) + })); + + // Custom sort to ensure Active > Planning > Completed - const sortedProjects = projects.sort((a, b) => { + const sortedProjects = projectsWithLikes.sort((a, b) => { const statusOrder: Record = { 'Active': 1, 'Planning': 2, diff --git a/src/components/LikeButton.tsx b/src/components/LikeButton.tsx index 44a4c65..6e8ac5a 100644 --- a/src/components/LikeButton.tsx +++ b/src/components/LikeButton.tsx @@ -1,16 +1,16 @@ "use client"; -import { useState } from 'react'; -import { FaHeart, FaRegHeart } from 'react-icons/fa'; -import { useToast } from '@/components/ui/toast-1'; -import { clsx } from 'clsx'; -import { useSession } from 'next-auth/react'; -import { LoginModal } from './LoginModal'; +import { useState } from "react"; +import { FaHeart, FaRegHeart } from "react-icons/fa"; +import { useToast } from "@/components/ui/toast-1"; +import { clsx } from "clsx"; +import { useSession } from "next-auth/react"; +import { LoginModal } from "./LoginModal"; interface LikeButtonProps { projectId: string; isInitiallyLiked: boolean; - initialLikeCount: number; + initialLikeCount: number; disabled?: boolean; } @@ -37,7 +37,7 @@ const LikeButton = ({ if (isLoading || disabled) return; setIsLoading(true); - + // --- 1. STORE PREVIOUS STATE FOR ROLLBACK --- const previousLikeState = isLiked; const previousLikeCount = likeCount; @@ -45,18 +45,19 @@ const LikeButton = ({ // --- 2. OPTIMISTIC UPDATE --- // Update both the icon and the count immediately setIsLiked(!previousLikeState); - setLikeCount(previousLikeState ? previousLikeCount - 1 : previousLikeCount + 1); + setLikeCount( + previousLikeState ? previousLikeCount - 1 : previousLikeCount + 1 + ); try { // 3. API CALL (same as before) const response = await fetch(`/api/projects/${projectId}/like`, { - method: 'POST', + method: "POST", }); if (!response.ok) { - throw new Error('Failed to update like status'); + throw new Error("Failed to update like status"); } - } catch (error) { console.error(error); @@ -64,8 +65,7 @@ const LikeButton = ({ // If the API call fails, revert both states setIsLiked(previousLikeState); setLikeCount(previousLikeCount); - showToast('Failed to update like status', 'error'); - + showToast("Failed to update like status", "error"); } finally { setIsLoading(false); } @@ -82,29 +82,27 @@ const LikeButton = ({ onClick={toggleLike} disabled={isDisabled} className={clsx( - 'inline-flex items-center justify-center transition-opacity h-4 w-4', // Added h-4 w-4 + "inline-flex items-center justify-center transition-opacity h-6 w-6", { - 'cursor-not-allowed opacity-70': isDisabled, - 'cursor-pointer': !isDisabled, + "cursor-not-allowed opacity-70": isDisabled, + "cursor-pointer": !isDisabled, } )} - aria-label={ - disabled ? 'Login to like' : isLiked ? 'Unlike' : 'Like' - } + aria-label={disabled ? "Login to like" : isLiked ? "Unlike" : "Like"} > {isLiked ? ( - + ) : ( - + )} - + {/* Render the likeCount state, which updates instantly */} - {likeCount} + {likeCount}
setLoginOpen(false)} /> ); }; -export default LikeButton; \ No newline at end of file +export default LikeButton; diff --git a/src/components/ProjectCard.tsx b/src/components/ProjectCard.tsx index 6a172e8..6556105 100644 --- a/src/components/ProjectCard.tsx +++ b/src/components/ProjectCard.tsx @@ -21,6 +21,7 @@ interface Project { projectStatus: "Planning" | "Active" | "Completed"; isActive: boolean; authorId: string; + isLiked: boolean; author: { id: string; name: string; @@ -136,11 +137,13 @@ export default function ProjectCard() { ref={index === projects.length - 1 ? lastProjectRef : null} > { + projectId: string; // Add projectId here title: string; tagline: string; description: string; status: "Planning" | "Active" | "Completed"; - likes: number; + initialLikeCount: number; + isInitiallyLiked: boolean; // comments: number; href: string; githubUrl?: string; // Optional GitHub URL @@ -99,13 +102,15 @@ const ProjectCard = React.forwardRef( tagline, description, status, - likes, + initialLikeCount, + isInitiallyLiked, // comments, href, githubUrl, techStack = [], isAuthenticated, onViewProject, + projectId, // Destructure projectId to prevent it from being passed to the div ...props }, ref @@ -121,18 +126,15 @@ const ProjectCard = React.forwardRef( }; checkMobile(); - window.addEventListener('resize', checkMobile); + window.addEventListener("resize", checkMobile); - return () => window.removeEventListener('resize', checkMobile); + return () => window.removeEventListener("resize", checkMobile); }, []); // Toggle expansion on mobile const handleCardClick = (e: React.MouseEvent) => { // Don't toggle if clicking on links or buttons - if ( - isMobile && - !(e.target as HTMLElement).closest('a, button') - ) { + if (isMobile && !(e.target as HTMLElement).closest("a, button")) { setIsExpanded(!isExpanded); } }; @@ -162,7 +164,6 @@ const ProjectCard = React.forwardRef( > {/* Content Container */}
- {/* Top Section: Status Badge and GitHub Link */}
@@ -240,63 +241,68 @@ const ProjectCard = React.forwardRef(
- - - {likes} -
{/* Tech Stack Section (shown on hover for desktop, on click for mobile) */} {techStack.length > 0 && ( -
+

TECH STACK

- {techStack.filter(tech => tech && tech.name).map((tech, index) => ( -
+ {techStack + .filter((tech) => tech && tech.name) + .map((tech, index) => (
- {tech?.icon || null} +
+ {tech?.icon || null} +
+ + {tech.name} +
- - {tech.name} - -
- ))} + ))}
)} - -
); @@ -304,4 +310,4 @@ const ProjectCard = React.forwardRef( ); ProjectCard.displayName = "ProjectCard"; -export { ProjectCard }; \ No newline at end of file +export { ProjectCard };