Skip to content

Commit

Permalink
Merge pull request #64 from deepraj21/main
Browse files Browse the repository at this point in the history
feat: added star to project
  • Loading branch information
deepraj21 authored Oct 22, 2024
2 parents bbe9a7a + 22b4913 commit c380192
Show file tree
Hide file tree
Showing 14 changed files with 454 additions and 488 deletions.
28 changes: 28 additions & 0 deletions client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"dependencies": {
"@hookform/resolvers": "^3.9.0",
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-alert-dialog": "^1.1.2",
"@radix-ui/react-avatar": "^1.1.1",
"@radix-ui/react-collapsible": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.2",
Expand Down
4 changes: 2 additions & 2 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ const App = () => {
<Route path="/home" element={<Home />} />
<Route path="/settings" element={<EditProfileForm />} />
<Route path="/message" element={<MessagePage/>} />
<Route path="/projects" element={<Projects />} />
<Route path="/u/:username" element={<Profile />} />
<Route path="/projects/:username" element={<Projects />} />
<Route path="/user/:username" element={<Profile />} />
<Route path="*" element={<div>404</div>} />
</Routes>
</Router>
Expand Down
Empty file.
92 changes: 92 additions & 0 deletions client/src/components/Projects/ProjectCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { useState, useEffect } from "react";
import { CircleIcon, StarIcon } from "@radix-ui/react-icons";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { FaStar as FilledStarIcon } from "react-icons/fa";

interface ProjectProps {
project: {
projectId: string;
title: string;
description: string;
repoLink: string;
starCount: number;
tags: string[];
};
}

const backendUrl = import.meta.env.VITE_BACKEND_URL || 'http://localhost:5000';

export function ProjectCard({ project }: ProjectProps) {
const [isStarred, setIsStarred] = useState(false);
const [starCount, setStarCount] = useState(project.starCount);
const username = localStorage.getItem('devhub_username');

// Fetch initial star state from server/localStorage or logic to check if user has starred
useEffect(() => {
const fetchStarState = async () => {
const starredStatus = localStorage.getItem(`starred_${project.projectId}`);
setIsStarred(!!starredStatus);
};
fetchStarState();
}, [project.projectId]);

const handleStarClick = async () => {
if (isStarred) return; // Prevent multiple stars

try {
const response = await fetch(`${backendUrl}/profile/${username}/projects/${project.projectId}/star`, {
method: "POST",
headers: { "Content-Type": "application/json" },
});

if (response.ok) {
setStarCount((prevCount) => prevCount + 1);
setIsStarred(true);
localStorage.setItem(`starred_${project.projectId}`, "true");
} else {
console.error("Failed to star the project");
}
} catch (error) {
console.error("Error starring the project:", error);
}
};

return (
<Card>
<CardHeader className="grid grid-cols-[1fr_110px] items-start gap-4 space-y-0">
<div className="space-y-1">
<CardTitle>{project.title}</CardTitle>
<CardDescription>{project.description}</CardDescription>
</div>
<div className="flex items-center rounded-md bg-secondary text-secondary-foreground">
<Button
variant="secondary"
className=" shadow-none"
onClick={handleStarClick}
disabled={isStarred}
>
{isStarred ? (
<FilledStarIcon className="mr-2 h-4 w-4 text-yellow-400" />
) : (
<StarIcon className="mr-2 h-4 w-4" />
)}
Star
</Button>
</div>
</CardHeader>
<CardContent>
<div className="flex space-x-4 text-sm text-muted-foreground">
<div className="flex items-center">
<CircleIcon className="mr-1 h-3 w-3 fill-sky-400 text-sky-400" />
{project.tags.join(", ") || "No Tags"}
</div>
<div className="flex items-center">
<FilledStarIcon className="mr-1 h-3 w-3 text-yellow-400" />
{starCount} Stars
</div>
</div>
</CardContent>
</Card>
);
}
59 changes: 58 additions & 1 deletion client/src/components/Projects/Projects.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@ import {
SidebarTrigger,
} from "@/components/ui/sidebar"
import { SidebarLeft } from '@/components/Sidebar/Sidebar'
import { useEffect, useState } from "react";
import { ProjectCard } from "@/components/Projects/ProjectCard";
import { useParams } from "react-router-dom";

const backendUrl = import.meta.env.VITE_BACKEND_URL || 'http://localhost:5000';

interface Project {
projectId: string;
title: string;
description: string;
repoLink: string;
starCount: number;
tags: string[];
}

const Projects = () => {
return (
Expand All @@ -23,10 +37,53 @@ const Projects = () => {
)
}

const fetchUserProjects = async (username: string) => {
const response = await fetch(`${backendUrl}/profile/${username}/projects`);
if (!response.ok) {
throw new Error("Failed to fetch projects");
}
return response.json();
};

const Dashboard = () => {
const [projects, setProjects] = useState<Project[]>([]);
const [loading, setLoading] = useState(true);
const { username } = useParams<{ username: string }>();

useEffect(() => {
const loadProjects = async () => {
try {
const data = await fetchUserProjects(username || "");
setProjects(data.projects || []);
} catch (error) {
console.error("Error fetching projects:", error);
} finally {
setLoading(false);
}
};

if (username) {
loadProjects();
}
}, [username]);

if (loading) {
return <div>Loading projects...</div>;
}

if (!projects.length) {
return <div>No projects found.</div>;
}
return(
<>
project
<div className="flex flex-1 flex-col gap-4 p-4">
<div className="grid auto-rows-min gap-4 md:grid-cols-3">
{projects.map((project) => (
<ProjectCard key={project.projectId} project={project} />
))}
</div>
</div>

</>
)
}
Expand Down
4 changes: 2 additions & 2 deletions client/src/components/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export function SidebarLeft({ ...props }: React.ComponentProps<typeof Sidebar>)
},
{
title: "Projects",
url: "/projects",
url: username ? `/projects/${username}` : "#",
icon: Inbox,
},
],
Expand All @@ -95,7 +95,7 @@ export function SidebarLeft({ ...props }: React.ComponentProps<typeof Sidebar>)
},
{
title: username ? `${username}` : "profile",
url: username ? `/u/${username}` : "#",
url: username ? `/user/${username}` : "#",
icon: CircleUser,
},
{
Expand Down
139 changes: 139 additions & 0 deletions client/src/components/ui/alert-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import * as React from "react"
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"

import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"

const AlertDialog = AlertDialogPrimitive.Root

const AlertDialogTrigger = AlertDialogPrimitive.Trigger

const AlertDialogPortal = AlertDialogPrimitive.Portal

const AlertDialogOverlay = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
ref={ref}
/>
))
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName

const AlertDialogContent = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
>(({ className, ...props }, ref) => (
<AlertDialogPortal>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-[90%] sm:w-auto translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
/>
</AlertDialogPortal>
))
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName

const AlertDialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-2 text-center sm:text-left",
className
)}
{...props}
/>
)
AlertDialogHeader.displayName = "AlertDialogHeader"

const AlertDialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
AlertDialogFooter.displayName = "AlertDialogFooter"

const AlertDialogTitle = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold", className)}
{...props}
/>
))
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName

const AlertDialogDescription = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
AlertDialogDescription.displayName =
AlertDialogPrimitive.Description.displayName

const AlertDialogAction = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Action>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Action
ref={ref}
className={cn(buttonVariants(), className)}
{...props}
/>
))
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName

const AlertDialogCancel = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Cancel
ref={ref}
className={cn(
buttonVariants({ variant: "outline" }),
"mt-2 sm:mt-0",
className
)}
{...props}
/>
))
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName

export {
AlertDialog,
AlertDialogPortal,
AlertDialogOverlay,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel,
}
Loading

0 comments on commit c380192

Please sign in to comment.