Skip to content

Commit e6a2748

Browse files
authored
Merge pull request #68 from deepraj21/main
Add image banner support to project
2 parents 9cbb5f9 + da11e5e commit e6a2748

File tree

15 files changed

+592
-160
lines changed

15 files changed

+592
-160
lines changed

client/src/App.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import EditProfileForm from './pages/EditProfileForm';
1010
import { MessagePage } from './pages/MessagePage';
1111
import Projects from './pages/Projects';
1212
import Visualization from './pages/Visualization';
13+
import PrivacyPolicy from './pages/Privacypolicy';
14+
import ProjectDisplay from './pages/ProjectDisplay';
1315

1416
const App = () => {
1517

@@ -24,8 +26,10 @@ const App = () => {
2426
<Route path="/settings" element={<EditProfileForm />} />
2527
<Route path="/message" element={<MessagePage/>} />
2628
<Route path="/projects/:username" element={<Projects />} />
29+
<Route path="/projects/:username/:projectId" element={<ProjectDisplay />} />
2730
<Route path="/user/:username" element={<Profile />} />
2831
<Route path="/relations/:username" element={<Visualization />} />
32+
<Route path="/privacy-policy" element={<PrivacyPolicy/>} />
2933
<Route path="*" element={<div>404</div>} />
3034
</Routes>
3135
</Router>

client/src/components/Footer/Footer.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ const Footer = () => {
2424
<ul className="text-gray-500 dark:text-gray-400 font-medium">
2525
<li className="mb-4">
2626
<a href="/" className="hover:underline">
27-
Devhub
27+
Posts
2828
</a>
2929
</li>
3030
<li>
3131
<a href="/" className="hover:underline">
32-
Tailwind CSS
32+
DevMap
3333
</a>
3434
</li>
3535
</ul>
@@ -63,7 +63,7 @@ const Footer = () => {
6363
</h2>
6464
<ul className="text-gray-500 dark:text-gray-400 font-medium">
6565
<li className="mb-4">
66-
<a href="#" className="hover:underline">
66+
<a href="/privacy-policy" className="hover:underline">
6767
Privacy Policy
6868
</a>
6969
</li>

client/src/components/Projects/AddProject.tsx

Lines changed: 92 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {
66
AlertDialogContent,
77
AlertDialogDescription,
88
AlertDialogHeader,
9-
AlertDialogTitle,
109
AlertDialogTrigger,
1110
} from "@/components/ui/alert-dialog";
1211
import { Cross1Icon } from "@radix-ui/react-icons";
@@ -16,6 +15,7 @@ import { toast } from 'sonner';
1615
import { Button } from "@/components/ui/button";
1716
import { Icons } from "@/components/ui/icons";
1817
import { Textarea } from '../ui/textarea';
18+
import UploadComponent from './UploadComponent';
1919

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

@@ -24,6 +24,7 @@ interface Project {
2424
description: string;
2525
repoLink: string;
2626
tags: Tag[];
27+
imageUrl?: string;
2728
}
2829

2930
interface Tag {
@@ -37,8 +38,10 @@ const AddProject: React.FC<{ onProjectChange: () => void }> = ({ onProjectChange
3738
description: '',
3839
repoLink: '',
3940
tags: [],
41+
imageUrl: '', // Add image URL state
4042
});
4143

44+
const [imageFile, setImageFile] = useState<File | null>(null); // State for handling image file
4245
const username = localStorage.getItem('devhub_username');
4346
const [isLoading, setIsLoading] = useState(false);
4447

@@ -50,16 +53,39 @@ const AddProject: React.FC<{ onProjectChange: () => void }> = ({ onProjectChange
5053
}));
5154
};
5255

56+
// Function to upload image to Cloudinary via the backend
57+
const uploadImage = async () => {
58+
if (!imageFile) return ''; // Return empty string if no image is uploaded
59+
60+
const formData = new FormData();
61+
formData.append('image', imageFile);
62+
63+
try {
64+
const response = await axios.post(`${backendUrl}/project/upload`, formData, {
65+
headers: {
66+
'Content-Type': 'multipart/form-data',
67+
},
68+
});
69+
70+
return response.data.imageUrl; // Return the image URL
71+
} catch (error) {
72+
console.error('Image upload failed:', error);
73+
toast.error('Failed to upload image');
74+
return '';
75+
}
76+
};
77+
5378
const handleAddProject = async () => {
5479
if (!username) {
5580
toast.error('Username not found');
5681
return;
5782
}
5883

59-
setIsLoading(true);
84+
setIsLoading(true);
6085

6186
try {
62-
// Convert tags array to a comma-separated string
87+
const imageUrl = await uploadImage(); // Upload the image and get the URL
88+
6389
const tagsString = newProject.tags.map((tag) => tag.value).join(',');
6490

6591
const response = await axios.post(
@@ -68,14 +94,16 @@ const AddProject: React.FC<{ onProjectChange: () => void }> = ({ onProjectChange
6894
title: newProject.title,
6995
description: newProject.description,
7096
repo_link: newProject.repoLink,
71-
tags: tagsString, // Send as comma-separated string
97+
tags: tagsString, // Send as comma-separated string
98+
imageUrl, // Include the uploaded image URL
7299
},
73100
{ withCredentials: true },
74101
);
75102

76103
if (response.status === 200 || response.status === 201) {
77104
toast.success('Project added successfully');
78-
setNewProject({ title: '', description: '', repoLink: '', tags: [] }); // Reset form
105+
setNewProject({ title: '', description: '', repoLink: '', tags: [], imageUrl: '' }); // Reset form
106+
setImageFile(null); // Reset the image file
79107
onProjectChange();
80108
} else {
81109
toast.error('Failed to add project');
@@ -84,80 +112,78 @@ const AddProject: React.FC<{ onProjectChange: () => void }> = ({ onProjectChange
84112
console.error('Failed to add project:', error);
85113
toast.error('An error occurred while adding the project');
86114
} finally {
87-
setIsLoading(false); // Reset loading state
115+
setIsLoading(false); // Reset loading state
88116
}
89117
};
90118

91-
92-
93-
94119
return (
95120
<AlertDialog>
96121
<AlertDialogTrigger className="w-[120px] ml-5 mt-5 mb-3">
97122
<Button variant="outline" className="h-[50px]">Add Project</Button>
98123
</AlertDialogTrigger>
99124
<AlertDialogContent>
100125
<div className='flex'>
101-
<AlertDialogHeader className='text-2xl'>Add New Project</AlertDialogHeader>
126+
<AlertDialogHeader className='text-2xl mt-1.5'>Add New Project</AlertDialogHeader>
102127
<div className='flex-grow'></div>
103-
<AlertDialogCancel><Cross1Icon className='h-3 w-3'/></AlertDialogCancel>
128+
<AlertDialogCancel><Cross1Icon className='h-3 w-3' /></AlertDialogCancel>
104129
</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>
130+
<AlertDialogDescription>
131+
<div className="grid gap-6 sm:w-80">
132+
<div>
133+
<UploadComponent onFileChange={setImageFile} />
134+
</div>
135+
<div>
136+
<Input
137+
id="newProjectTitle"
138+
name="title"
139+
value={newProject.title}
140+
onChange={handleProjectChange}
141+
disabled={isLoading}
142+
placeholder="Project title"
143+
className="mt-2"
144+
/>
145+
</div>
146+
<div>
147+
<Textarea
148+
id="newProjectDescription"
149+
name="description"
150+
value={newProject.description}
151+
onChange={handleProjectChange}
152+
disabled={isLoading}
153+
placeholder="# Project description"
154+
className=""
155+
/>
156+
</div>
157+
<div>
158+
<TagInput
159+
selectedTags={newProject.tags}
160+
onTagsChange={(tags) => setNewProject({ ...newProject, tags })}
161+
/>
162+
</div>
163+
<div>
164+
<Input
165+
id="newProjectRepoLink"
166+
name="repoLink"
167+
value={newProject.repoLink}
168+
onChange={handleProjectChange}
169+
disabled={isLoading}
170+
placeholder="Repository link"
171+
className=""
172+
/>
173+
</div>
159174

160-
</AlertDialogDescription>
175+
<Button onClick={handleAddProject} disabled={isLoading} className="w-full mt-4">
176+
{isLoading ? (
177+
<>
178+
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
179+
Adding...
180+
</>
181+
) : (
182+
'Add Project'
183+
)}
184+
</Button>
185+
</div>
186+
</AlertDialogDescription>
161187
</AlertDialogContent>
162188
</AlertDialog>
163189
);

client/src/components/Projects/ProjectCard.tsx

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useState, useEffect } from "react";
22
import { StarIcon, GitHubLogoIcon, TrashIcon, Pencil2Icon } from "@radix-ui/react-icons";
33
import { Button } from "@/components/ui/button";
4-
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
4+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
55
import { FaStar as FilledStarIcon } from "react-icons/fa";
66
import {
77
AlertDialog,
@@ -14,13 +14,17 @@ import {
1414
import UpdateProject from "./UpdateProject";
1515
import DeleteProject from "./DeleteProject";
1616
import { Cross1Icon } from "@radix-ui/react-icons";
17+
import { Skeleton } from "../ui/skeleton";
18+
import { useNavigate } from "react-router-dom";
19+
import ReactMarkdown from 'react-markdown';
1720

1821
interface Project {
1922
projectId: string;
2023
title: string;
2124
description: string;
2225
repoLink: string;
2326
starCount: number;
27+
imageUrl: string;
2428
tags: string[];
2529
}
2630

@@ -34,7 +38,13 @@ const backendUrl = import.meta.env.VITE_BACKEND_URL || 'http://localhost:5000';
3438
export function ProjectCard({ project, onProjectChange }: ProjectProps) {
3539
const [isStarred, setIsStarred] = useState(false);
3640
const [starCount, setStarCount] = useState(project.starCount);
37-
const username = localStorage.getItem('devhub_username');
41+
const username = localStorage.getItem('devhub_username');
42+
43+
const navigate = useNavigate();
44+
45+
const projectDetails = () => {
46+
navigate(`/projects/${username}/${project.projectId}`);
47+
}
3848

3949
// Fetch initial star state from server/localStorage or logic to check if user has starred
4050
useEffect(() => {
@@ -68,10 +78,24 @@ export function ProjectCard({ project, onProjectChange }: ProjectProps) {
6878

6979
return (
7080
<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+
}
7189
<CardHeader className="grid grid-cols-[1fr_110px] items-start gap-4 space-y-0">
90+
7291
<div className="space-y-1">
73-
<CardTitle>{project.title}</CardTitle>
74-
<CardDescription>{project.description}</CardDescription>
92+
<CardTitle
93+
onClick={projectDetails}
94+
className="hover:underline cursor-pointer"
95+
>
96+
{project.title}
97+
</CardTitle>
98+
<ReactMarkdown>{project.description}</ReactMarkdown>
7599
</div>
76100
<div className="flex items-center rounded-md bg-secondary text-secondary-foreground">
77101
<Button

0 commit comments

Comments
 (0)