Skip to content

Commit 837e5db

Browse files
authored
Merge pull request #69 from deepraj21/main
Added star project on projectDisplay
2 parents e6a2748 + 75b1d09 commit 837e5db

File tree

2 files changed

+154
-67
lines changed

2 files changed

+154
-67
lines changed

client/src/components/Projects/ProjectCard.tsx

Lines changed: 67 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import UpdateProject from "./UpdateProject";
1515
import DeleteProject from "./DeleteProject";
1616
import { Cross1Icon } from "@radix-ui/react-icons";
1717
import { Skeleton } from "../ui/skeleton";
18-
import { useNavigate } from "react-router-dom";
18+
import { useNavigate, useParams } from "react-router-dom";
1919
import ReactMarkdown from 'react-markdown';
2020

2121
interface Project {
@@ -38,13 +38,16 @@ const backendUrl = import.meta.env.VITE_BACKEND_URL || 'http://localhost:5000';
3838
export function ProjectCard({ project, onProjectChange }: ProjectProps) {
3939
const [isStarred, setIsStarred] = useState(false);
4040
const [starCount, setStarCount] = useState(project.starCount);
41-
const username = localStorage.getItem('devhub_username');
41+
const username = localStorage.getItem('devhub_username'); // Logged-in user
42+
const { username: paramsUsername } = useParams<{ username: string }>(); // Extract username from URL
43+
44+
const isProjectOwner = username === paramsUsername; // Check if the logged-in user is the project owner
4245

4346
const navigate = useNavigate();
44-
47+
4548
const projectDetails = () => {
46-
navigate(`/projects/${username}/${project.projectId}`);
47-
}
49+
navigate(`/projects/${paramsUsername}/${project.projectId}`);
50+
};
4851

4952
// Fetch initial star state from server/localStorage or logic to check if user has starred
5053
useEffect(() => {
@@ -59,7 +62,7 @@ export function ProjectCard({ project, onProjectChange }: ProjectProps) {
5962
if (isStarred) return; // Prevent multiple stars
6063

6164
try {
62-
const response = await fetch(`${backendUrl}/profile/${username}/projects/${project.projectId}/star`, {
65+
const response = await fetch(`${backendUrl}/profile/${paramsUsername}/projects/${project.projectId}/star`, {
6366
method: "POST",
6467
headers: { "Content-Type": "application/json" },
6568
});
@@ -78,19 +81,18 @@ export function ProjectCard({ project, onProjectChange }: ProjectProps) {
7881

7982
return (
8083
<Card>
81-
{
82-
project.imageUrl ? (
83-
<div className="h-40 w-full bg-primary rounded-tl-md rounded-tr-md" style={{backgroundImage: `url(${project.imageUrl})`}}></div>
84-
):
85-
(
86-
<Skeleton className="h-40 w-full rounded-tl-md rounded-tr-md" />
87-
)
88-
}
84+
{project.imageUrl ? (
85+
<div
86+
className="h-40 w-full bg-primary rounded-tl-md rounded-tr-md"
87+
style={{ backgroundImage: `url(${project.imageUrl})` }}
88+
></div>
89+
) : (
90+
<Skeleton className="h-40 w-full rounded-tl-md rounded-tr-md" />
91+
)}
8992
<CardHeader className="grid grid-cols-[1fr_110px] items-start gap-4 space-y-0">
90-
9193
<div className="space-y-1">
92-
<CardTitle
93-
onClick={projectDetails}
94+
<CardTitle
95+
onClick={projectDetails}
9496
className="hover:underline cursor-pointer"
9597
>
9698
{project.title}
@@ -109,53 +111,66 @@ export function ProjectCard({ project, onProjectChange }: ProjectProps) {
109111
) : (
110112
<StarIcon className="mr-2 h-4 w-4" />
111113
)}
112-
Star
114+
Star
113115
</Button>
114116
</div>
115117
</CardHeader>
116118
<CardContent>
117119
<div className="flex space-x-4 text-sm text-muted-foreground">
118120
<div className="flex items-center">
119121
<GitHubLogoIcon className="mr-1" />
120-
<a href={project.repoLink} target="_blank">visit</a>
122+
<a href={project.repoLink} target="_blank" rel="noopener noreferrer">
123+
visit
124+
</a>
121125
</div>
122126
<div className="flex items-center">
123127
<StarIcon className="mr-1" />
124-
{starCount === null ? "0" : starCount} Stars
128+
{starCount} Stars
125129
</div>
126130
<div className="flex-grow"></div>
127-
<div className="flex items-center">
128-
<AlertDialog>
129-
<AlertDialogTrigger>
130-
<Pencil2Icon className="mr-2 h-5 w-5" />
131-
</AlertDialogTrigger>
132-
<AlertDialogContent>
133-
<div className='flex'>
134-
<AlertDialogHeader className='text-2xl'>Update Project</AlertDialogHeader>
135-
<div className='flex-grow'></div>
136-
<AlertDialogCancel><Cross1Icon className='h-3 w-3' /></AlertDialogCancel>
137-
</div>
138-
<UpdateProject project={project} onProjectChange={onProjectChange}/>
139-
</AlertDialogContent>
140-
</AlertDialog>
141-
<AlertDialog>
142-
<AlertDialogTrigger>
143-
<TrashIcon className="mr-1 h-5 w-5 text-red-600" />
144-
</AlertDialogTrigger>
145-
<AlertDialogContent>
146-
<div className="flex">
147-
<AlertDialogHeader className='text-2xl'>Delete Project</AlertDialogHeader>
148-
<div className='flex-grow'></div>
149-
<AlertDialogCancel><Cross1Icon className='h-3 w-3' /></AlertDialogCancel>
150-
</div>
151-
<AlertDialogDescription>
152-
Are you sure?<br/>
153-
This action cannot be undone.
154-
</AlertDialogDescription>
155-
<DeleteProject projectId={project.projectId} onProjectChange={onProjectChange}/>
156-
</AlertDialogContent>
157-
</AlertDialog>
158-
</div>
131+
132+
{/* Only show edit and delete options if the logged-in user is the project owner */}
133+
{isProjectOwner && (
134+
<div className="flex items-center">
135+
<AlertDialog>
136+
<AlertDialogTrigger>
137+
<Pencil2Icon className="mr-2 h-5 w-5" />
138+
</AlertDialogTrigger>
139+
<AlertDialogContent>
140+
<div className="flex">
141+
<AlertDialogHeader className="text-2xl">
142+
Update Project
143+
</AlertDialogHeader>
144+
<div className="flex-grow"></div>
145+
<AlertDialogCancel>
146+
<Cross1Icon className="h-3 w-3" />
147+
</AlertDialogCancel>
148+
</div>
149+
<UpdateProject project={project} onProjectChange={onProjectChange} />
150+
</AlertDialogContent>
151+
</AlertDialog>
152+
<AlertDialog>
153+
<AlertDialogTrigger>
154+
<TrashIcon className="mr-1 h-5 w-5 text-red-600" />
155+
</AlertDialogTrigger>
156+
<AlertDialogContent>
157+
<div className="flex">
158+
<AlertDialogHeader className="text-2xl">
159+
Delete Project
160+
</AlertDialogHeader>
161+
<div className="flex-grow"></div>
162+
<AlertDialogCancel>
163+
<Cross1Icon className="h-3 w-3" />
164+
</AlertDialogCancel>
165+
</div>
166+
<AlertDialogDescription>
167+
Are you sure? This action cannot be undone.
168+
</AlertDialogDescription>
169+
<DeleteProject projectId={project.projectId} onProjectChange={onProjectChange} />
170+
</AlertDialogContent>
171+
</AlertDialog>
172+
</div>
173+
)}
159174
</div>
160175
</CardContent>
161176
</Card>

client/src/pages/ProjectDisplay.tsx

Lines changed: 87 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,19 @@ import {
22
SidebarInset,
33
SidebarProvider,
44
SidebarTrigger,
5-
} from "@/components/ui/sidebar"
6-
import { SidebarLeft } from '@/components/Sidebar/Sidebar'
5+
} from "@/components/ui/sidebar";
6+
import { SidebarLeft } from "@/components/Sidebar/Sidebar";
7+
import { StarIcon } from "@radix-ui/react-icons";
78
import { useEffect, useState } from "react";
89
import { useParams } from "react-router-dom";
9-
import { Skeleton } from '@/components/ui/skeleton';
10-
import axios from 'axios';
11-
import ReactMarkdown from 'react-markdown';
10+
import { Skeleton } from "@/components/ui/skeleton";
11+
import axios from "axios";
12+
import ReactMarkdown from "react-markdown";
13+
import { FaStar as FilledStarIcon } from "react-icons/fa";
14+
import { Button } from "@/components/ui/button"; // Import the Button component
15+
import { toast } from "sonner"; // Assuming you use this for notifications
1216

13-
const backendUrl = import.meta.env.VITE_BACKEND_URL || 'http://localhost:5000';
17+
const backendUrl = import.meta.env.VITE_BACKEND_URL || "http://localhost:5000";
1418

1519
interface Project {
1620
projectId: string;
@@ -27,13 +31,18 @@ const ProjectDisplay = () => {
2731

2832
const [project, setProject] = useState<Project | null>(null);
2933
const [loading, setLoading] = useState(true);
34+
const [isStarred, setIsStarred] = useState(false);
35+
const [starCount, setStarCount] = useState(0); // Initialize as 0
36+
const [isStarRequestInProgress, setIsStarRequestInProgress] = useState(false); // To disable button during request
3037

3138
useEffect(() => {
3239
const fetchProject = async () => {
3340
try {
3441
const response = await axios.get(`${backendUrl}/profile/${username}/projects/${projectId}`);
3542
if (response.status === 200) {
36-
setProject(response.data);
43+
const projectData = response.data;
44+
setProject(projectData);
45+
setStarCount(projectData.starCount); // Set star count when project data is fetched
3746
} else {
3847
console.error("Failed to fetch project details");
3948
}
@@ -47,6 +56,41 @@ const ProjectDisplay = () => {
4756
fetchProject();
4857
}, [username, projectId]);
4958

59+
useEffect(() => {
60+
if (project?.projectId) {
61+
const starredStatus = localStorage.getItem(`starred_${project.projectId}`);
62+
setIsStarred(!!starredStatus);
63+
}
64+
}, [project?.projectId]);
65+
66+
const handleStarClick = async () => {
67+
if (isStarred || isStarRequestInProgress || !project) return; // Prevent multiple stars or if request is in progress
68+
69+
setIsStarRequestInProgress(true); // Disable the button while the request is in progress
70+
71+
try {
72+
const response = await fetch(`${backendUrl}/profile/${username}/projects/${project.projectId}/star`, {
73+
method: "POST",
74+
headers: { "Content-Type": "application/json" },
75+
});
76+
77+
if (response.ok) {
78+
setStarCount((prevCount) => prevCount + 1);
79+
setIsStarred(true);
80+
localStorage.setItem(`starred_${project.projectId}`, "true");
81+
toast.success("Project starred!");
82+
} else {
83+
console.error("Failed to star the project");
84+
toast.error("Failed to star the project");
85+
}
86+
} catch (error) {
87+
console.error("Error starring the project:", error);
88+
toast.error("Error starring the project");
89+
} finally {
90+
setIsStarRequestInProgress(false); // Re-enable the button after request finishes
91+
}
92+
};
93+
5094
return (
5195
<SidebarProvider>
5296
<SidebarLeft />
@@ -64,30 +108,58 @@ const ProjectDisplay = () => {
64108
<div className="space-y-4">
65109
{project.imageUrl && (
66110
<div className="md:h-[440px] sm:h-auto overflow-hidden">
67-
<img src={project.imageUrl} alt={project.title} className="w-full h-full object-cover" />
111+
<img
112+
src={project.imageUrl}
113+
alt={project.title}
114+
className="w-full h-full object-cover"
115+
/>
68116
</div>
69117
)}
70118
<div className="w-full p-6">
71-
<div className='flex'>
119+
<div className="flex items-center">
72120
<h1 className="text-4xl font-bold">{project.title}</h1>
73-
<div className='flex-grow'></div>
74-
<div className="p-2 flex items-center">
75-
<p className="text-lg text-gray-500">Stars: {project.starCount}</p>
121+
<div className="flex-grow"></div>
122+
<div className="p-2 flex items-center">
123+
<Button
124+
variant="secondary"
125+
className="shadow-none"
126+
onClick={handleStarClick}
127+
disabled={isStarred || isStarRequestInProgress} // Disable during request
128+
>
129+
{isStarred ? (
130+
<FilledStarIcon className="mr-2 h-4 w-4 text-yellow-400" />
131+
) : (
132+
<StarIcon className="mr-2 h-4 w-4" />
133+
)}
134+
{starCount}
135+
</Button>
76136
</div>
77137
</div>
78138
<div className="flex items-center mt-2">
79139
<h2 className="text-lg font-medium mr-2">Tags:</h2>
80140
<div className="flex gap-2">
81141
{project.tags.map((tag, index) => (
82-
<span key={index} className="px-2 py-1 dark:bg-gray-200 rounded-full text-sm dark:text-black">
142+
<span
143+
key={index}
144+
className="px-2 py-1 dark:bg-gray-200 rounded-full text-sm dark:text-black"
145+
>
83146
{tag}
84147
</span>
85148
))}
86149
</div>
87150
</div>
88-
<p className="text-lg text-gray-500 mt-2">Repo: <a href={project.repoLink} target="_blank" rel="noopener noreferrer" className="text-blue-600">{project.repoLink}</a></p>
151+
<p className="text-lg text-gray-500 mt-2">
152+
<a
153+
href={project.repoLink}
154+
target="_blank"
155+
rel="noopener noreferrer"
156+
className="text-blue-600"
157+
>
158+
Github Repository
159+
</a>
160+
</p>
89161
<h1 className="text-4xl font-bold mt-5">Project Description:</h1>
90-
<ReactMarkdown className="mt-4">{project.description}</ReactMarkdown>
162+
<ReactMarkdown className="mt-4">{project.description}</ReactMarkdown>
91163
</div>
92164
</div>
93165
) : (

0 commit comments

Comments
 (0)