Skip to content

Commit c579020

Browse files
authored
Merge pull request #65 from deepraj21/main
fix: update and delete project
2 parents c380192 + 38c6329 commit c579020

File tree

5 files changed

+466
-66
lines changed

5 files changed

+466
-66
lines changed
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import React, { useState } from 'react';
2+
import axios from 'axios';
3+
import {
4+
AlertDialog,
5+
AlertDialogCancel,
6+
AlertDialogContent,
7+
AlertDialogDescription,
8+
AlertDialogHeader,
9+
AlertDialogTitle,
10+
AlertDialogTrigger,
11+
} from "@/components/ui/alert-dialog";
12+
import { Cross1Icon } from "@radix-ui/react-icons";
13+
import { Input } from '@/components/ui/input';
14+
import TagInput from '@/components/MultiSelect/TagInput';
15+
import { toast } from 'sonner';
16+
import { Button } from "@/components/ui/button";
17+
import { Icons } from "@/components/ui/icons";
18+
import { Textarea } from '../ui/textarea';
19+
20+
const backendUrl = import.meta.env.VITE_BACKEND_URL || 'http://localhost:5000';
21+
22+
interface Project {
23+
title: string;
24+
description: string;
25+
repoLink: string;
26+
tags: Tag[];
27+
}
28+
29+
interface Tag {
30+
value: string;
31+
label: string;
32+
}
33+
34+
const AddProject: React.FC<{ onProjectChange: () => void }> = ({ onProjectChange }) => {
35+
const [newProject, setNewProject] = useState<Project>({
36+
title: '',
37+
description: '',
38+
repoLink: '',
39+
tags: [],
40+
});
41+
42+
const username = localStorage.getItem('devhub_username');
43+
const [isLoading, setIsLoading] = useState(false);
44+
45+
const handleProjectChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
46+
const { name, value } = e.target;
47+
setNewProject((prevProject) => ({
48+
...prevProject,
49+
[name]: value,
50+
}));
51+
};
52+
53+
const handleAddProject = async () => {
54+
if (!username) {
55+
toast.error('Username not found');
56+
return;
57+
}
58+
59+
setIsLoading(true);
60+
61+
try {
62+
// Convert tags array to a comma-separated string
63+
const tagsString = newProject.tags.map((tag) => tag.value).join(',');
64+
65+
const response = await axios.post(
66+
`${backendUrl}/profile/${username}/projects`,
67+
{
68+
title: newProject.title,
69+
description: newProject.description,
70+
repo_link: newProject.repoLink,
71+
tags: tagsString, // Send as comma-separated string
72+
},
73+
{ withCredentials: true },
74+
);
75+
76+
if (response.status === 200 || response.status === 201) {
77+
toast.success('Project added successfully');
78+
setNewProject({ title: '', description: '', repoLink: '', tags: [] }); // Reset form
79+
onProjectChange();
80+
} else {
81+
toast.error('Failed to add project');
82+
}
83+
} catch (error) {
84+
console.error('Failed to add project:', error);
85+
toast.error('An error occurred while adding the project');
86+
} finally {
87+
setIsLoading(false); // Reset loading state
88+
}
89+
};
90+
91+
92+
93+
94+
return (
95+
<AlertDialog>
96+
<AlertDialogTrigger className="w-[120px] ml-5 mt-5 mb-3">
97+
<Button variant="outline" className="h-[50px]">Add Project</Button>
98+
</AlertDialogTrigger>
99+
<AlertDialogContent>
100+
<div className='flex'>
101+
<AlertDialogHeader className='text-2xl'>Add New Project</AlertDialogHeader>
102+
<div className='flex-grow'></div>
103+
<AlertDialogCancel><Cross1Icon className='h-3 w-3'/></AlertDialogCancel>
104+
</div>
105+
<AlertDialogDescription>
106+
<AlertDialogTitle></AlertDialogTitle>
107+
<div className="grid gap-6 sm:w-80">
108+
<div>
109+
<Input
110+
id="newProjectTitle"
111+
name="title"
112+
value={newProject.title}
113+
onChange={handleProjectChange}
114+
disabled={isLoading}
115+
placeholder="Project title"
116+
className="mt-2"
117+
/>
118+
</div>
119+
<div>
120+
<Textarea
121+
id="newProjectDescription"
122+
name="description"
123+
value={newProject.description}
124+
onChange={handleProjectChange}
125+
disabled={isLoading}
126+
placeholder="# Project description"
127+
className=""
128+
/>
129+
</div>
130+
<div>
131+
<TagInput
132+
selectedTags={newProject.tags}
133+
onTagsChange={(tags) => setNewProject({ ...newProject, tags })}
134+
/>
135+
</div>
136+
<div>
137+
138+
<Input
139+
id="newProjectRepoLink"
140+
name="repoLink"
141+
value={newProject.repoLink}
142+
onChange={handleProjectChange}
143+
disabled={isLoading}
144+
placeholder="Repository link"
145+
className=""
146+
/>
147+
</div>
148+
<Button onClick={handleAddProject} disabled={isLoading} className="w-full mt-4">
149+
{isLoading ? (
150+
<>
151+
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
152+
Adding...
153+
</>
154+
) : (
155+
'Add Project'
156+
)}
157+
</Button>
158+
</div>
159+
160+
</AlertDialogDescription>
161+
</AlertDialogContent>
162+
</AlertDialog>
163+
);
164+
};
165+
166+
export default AddProject;
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import React, { useState } from 'react';
2+
import axios from 'axios';
3+
import { Button } from '@/components/ui/button';
4+
import { toast } from 'sonner';
5+
import { Icons } from "@/components/ui/icons";
6+
7+
const backendUrl = import.meta.env.VITE_BACKEND_URL || 'http://localhost:5000';
8+
9+
interface DeleteProjectProps {
10+
projectId: string;
11+
onProjectChange: () => void;
12+
}
13+
14+
const DeleteProject: React.FC<DeleteProjectProps> = ({ projectId, onProjectChange }) => {
15+
const username = localStorage.getItem('devhub_username');
16+
const [isLoading, setIsLoading] = useState(false);
17+
18+
const handleDeleteProject = async () => {
19+
setIsLoading(true);
20+
try {
21+
const response = await axios.delete(
22+
`${backendUrl}/profile/${username}/projects/${projectId}`,
23+
{ withCredentials: true }
24+
);
25+
26+
if (response.status === 200) {
27+
toast.success('Project deleted successfully');
28+
onProjectChange();
29+
} else {
30+
toast.error('Failed to delete project');
31+
}
32+
} catch (error) {
33+
console.error('Failed to delete project:', error);
34+
toast.error('An error occurred while deleting the project');
35+
} finally {
36+
setIsLoading(false);
37+
}
38+
};
39+
40+
return (
41+
<div className="grid gap-6 sm:w-80">
42+
<Button onClick={handleDeleteProject} disabled={isLoading} className="w-full mt-4" variant="destructive">
43+
{isLoading ? (
44+
<>
45+
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
46+
Deleting...
47+
</>
48+
) : (
49+
'Delete Project'
50+
)}
51+
</Button>
52+
</div>
53+
54+
);
55+
};
56+
57+
export default DeleteProject;

client/src/components/Projects/ProjectCard.tsx

Lines changed: 61 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,37 @@
11
import { useState, useEffect } from "react";
2-
import { CircleIcon, StarIcon } from "@radix-ui/react-icons";
2+
import { StarIcon, GitHubLogoIcon, TrashIcon, Pencil2Icon } from "@radix-ui/react-icons";
33
import { Button } from "@/components/ui/button";
44
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
55
import { FaStar as FilledStarIcon } from "react-icons/fa";
6+
import {
7+
AlertDialog,
8+
AlertDialogCancel,
9+
AlertDialogHeader,
10+
AlertDialogContent,
11+
AlertDialogTrigger,
12+
AlertDialogDescription
13+
} from "@/components/ui/alert-dialog";
14+
import UpdateProject from "./UpdateProject";
15+
import DeleteProject from "./DeleteProject";
16+
import { Cross1Icon } from "@radix-ui/react-icons";
17+
18+
interface Project {
19+
projectId: string;
20+
title: string;
21+
description: string;
22+
repoLink: string;
23+
starCount: number;
24+
tags: string[];
25+
}
626

727
interface ProjectProps {
8-
project: {
9-
projectId: string;
10-
title: string;
11-
description: string;
12-
repoLink: string;
13-
starCount: number;
14-
tags: string[];
15-
};
28+
project: Project;
29+
onProjectChange: () => void;
1630
}
1731

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

20-
export function ProjectCard({ project }: ProjectProps) {
34+
export function ProjectCard({ project, onProjectChange }: ProjectProps) {
2135
const [isStarred, setIsStarred] = useState(false);
2236
const [starCount, setStarCount] = useState(project.starCount);
2337
const username = localStorage.getItem('devhub_username');
@@ -78,12 +92,45 @@ export function ProjectCard({ project }: ProjectProps) {
7892
<CardContent>
7993
<div className="flex space-x-4 text-sm text-muted-foreground">
8094
<div className="flex items-center">
81-
<CircleIcon className="mr-1 h-3 w-3 fill-sky-400 text-sky-400" />
82-
{project.tags.join(", ") || "No Tags"}
95+
<GitHubLogoIcon className="mr-1" />
96+
<a href={project.repoLink} target="_blank">visit</a>
97+
</div>
98+
<div className="flex items-center">
99+
<StarIcon className="mr-1" />
100+
{starCount === null ? "0" : starCount} Stars
83101
</div>
102+
<div className="flex-grow"></div>
84103
<div className="flex items-center">
85-
<FilledStarIcon className="mr-1 h-3 w-3 text-yellow-400" />
86-
{starCount} Stars
104+
<AlertDialog>
105+
<AlertDialogTrigger>
106+
<Pencil2Icon className="mr-2 h-5 w-5" />
107+
</AlertDialogTrigger>
108+
<AlertDialogContent>
109+
<div className='flex'>
110+
<AlertDialogHeader className='text-2xl'>Update Project</AlertDialogHeader>
111+
<div className='flex-grow'></div>
112+
<AlertDialogCancel><Cross1Icon className='h-3 w-3' /></AlertDialogCancel>
113+
</div>
114+
<UpdateProject project={project} onProjectChange={onProjectChange}/>
115+
</AlertDialogContent>
116+
</AlertDialog>
117+
<AlertDialog>
118+
<AlertDialogTrigger>
119+
<TrashIcon className="mr-1 h-5 w-5 text-red-600" />
120+
</AlertDialogTrigger>
121+
<AlertDialogContent>
122+
<div className="flex">
123+
<AlertDialogHeader className='text-2xl'>Delete Project</AlertDialogHeader>
124+
<div className='flex-grow'></div>
125+
<AlertDialogCancel><Cross1Icon className='h-3 w-3' /></AlertDialogCancel>
126+
</div>
127+
<AlertDialogDescription>
128+
Are you sure?<br/>
129+
This action cannot be undone.
130+
</AlertDialogDescription>
131+
<DeleteProject projectId={project.projectId} onProjectChange={onProjectChange}/>
132+
</AlertDialogContent>
133+
</AlertDialog>
87134
</div>
88135
</div>
89136
</CardContent>

0 commit comments

Comments
 (0)