Skip to content

Commit a5fcc46

Browse files
authored
Merge pull request #827 from Yaddalapalli-Charan-Kumar-Naidu/ResourceSharing
Resource sharing hub
2 parents 6976964 + b11e475 commit a5fcc46

File tree

5 files changed

+222
-1
lines changed

5 files changed

+222
-1
lines changed

package-lock.json

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/App.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,12 @@ import UsefulRepo from './Page/ResoucesHub/UsefulRepo.jsx';
4646
import ResearchPaper from './Page/ResoucesHub/ResearchPaper.jsx';
4747
import APIs from './Page/ResoucesHub/APIs.jsx';
4848
import CodingPlatforms from './Page/ResoucesHub/CodingPlatforms.jsx';
49+
import ResourceSharingHub from './Page/ResourceSharingHub.jsx';
4950

5051
import PageNotFound from './Page/PageNotFound.jsx';
5152
import ProfilePage from './components/Profile/ProfilePage';
5253

54+
5355
function App() {
5456
React.useEffect(() => {
5557
document.documentElement.classList.add('dark');
@@ -139,6 +141,7 @@ function App() {
139141
<Route path="/ResearchPaper" element={<ResearchPaper />} />
140142
<Route path="/APIs" element={<APIs />} />
141143
<Route path="/CodingPlatforms" element={<CodingPlatforms />} />
144+
<Route path="/ResourceSharing" element={<ResourceSharingHub />} />
142145

143146
<Route path="/profile/:name" element={<ProfilePage />} />
144147
<Route path="*" element={<PageNotFound />} />

src/Page/PageNotFound.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const PageNotFound = () => {
1010
Oops! The page you're looking for doesn't exist or has been moved.
1111
</p>
1212
<Link
13-
to="/"
13+
to="/Home"
1414
className="mt-8 inline-block rounded-lg bg-textSecondary px-6 py-3 font-poppoins font-semibold text-white transition duration-300 hover:bg-blue-700"
1515
>
1616
Go Back Home

src/Page/ResourceSharingHub.jsx

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import React, { useState, useEffect } from 'react';
2+
import useDebounce from '../hooks/useDebouncer';
3+
import { FaThumbsUp, FaThumbsDown, FaLink, FaPlus } from 'react-icons/fa';
4+
5+
const ResourceSharingHub = () => {
6+
const [resources, setResources] = useState([]);
7+
const [filteredResources, setFilteredResources] = useState([]);
8+
const [searchQuery, setSearchQuery] = useState('');
9+
const [newResource, setNewResource] = useState({ title: '', description: '', link: '', tags: '' });
10+
const [isModalOpen, setIsModalOpen] = useState(false);
11+
const [userVotes, setUserVotes] = useState({});
12+
const debouncedSearchQuery = useDebounce(searchQuery, 300);
13+
14+
useEffect(() => {
15+
const savedResources = JSON.parse(localStorage.getItem('resources')) || [];
16+
setResources(savedResources);
17+
setFilteredResources(savedResources);
18+
}, []);
19+
20+
useEffect(() => {
21+
const filtered = resources.filter(
22+
(resource) =>
23+
resource.title.toLowerCase().includes(debouncedSearchQuery.toLowerCase()) ||
24+
resource.tags.some((tag) => tag.toLowerCase().includes(debouncedSearchQuery.toLowerCase())),
25+
);
26+
setFilteredResources(filtered);
27+
}, [debouncedSearchQuery, resources]);
28+
29+
const handleVote = (index, type) => {
30+
const updatedResources = [...resources];
31+
const currentVote = userVotes[index];
32+
33+
if (currentVote === type) {
34+
updatedResources[index][`${type}s`] -= 1;
35+
setUserVotes({ ...userVotes, [index]: null });
36+
} else {
37+
if (currentVote) {
38+
updatedResources[index][`${currentVote}s`] -= 1;
39+
}
40+
updatedResources[index][`${type}s`] += 1;
41+
setUserVotes({ ...userVotes, [index]: type });
42+
}
43+
44+
setResources(updatedResources);
45+
localStorage.setItem('resources', JSON.stringify(updatedResources));
46+
};
47+
48+
const handleSubmit = (e) => {
49+
e.preventDefault();
50+
const newEntry = {
51+
...newResource,
52+
tags: newResource.tags.split(',').map((tag) => tag.trim()),
53+
upvotes: 0,
54+
downvotes: 0,
55+
};
56+
const updatedResources = [newEntry, ...resources];
57+
setResources(updatedResources);
58+
setFilteredResources(updatedResources);
59+
localStorage.setItem('resources', JSON.stringify(updatedResources));
60+
setNewResource({ title: '', description: '', link: '', tags: '' });
61+
setIsModalOpen(false);
62+
};
63+
64+
return (
65+
<div className="min-h-screen bg-gray-900 p-6 text-white">
66+
<header className="mb-6 w-full rounded-md bg-[#00a6fb] py-4 text-center">
67+
<h1 className="text-4xl font-bold">Resource Sharing Hub</h1>
68+
<p className="mt-2 text-sm">Share and explore valuable resources for developers.</p>
69+
</header>
70+
71+
<div className="mb-6 flex items-center justify-center gap-4">
72+
<input
73+
type="text"
74+
value={searchQuery}
75+
onChange={(e) => setSearchQuery(e.target.value)}
76+
placeholder="Search resources..."
77+
className="w-full rounded-lg border border-gray-700 bg-gray-800 px-4 py-2 text-white focus:ring focus:ring-[#00a6fb] sm:w-1/3"
78+
/>
79+
<button
80+
onClick={() => setIsModalOpen(true)}
81+
className="flex items-center gap-2 rounded bg-[#00a6fb] p-2 text-white hover:bg-[#0096e6]"
82+
>
83+
<FaPlus /> Add Resource
84+
</button>
85+
</div>
86+
87+
{isModalOpen && (
88+
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50">
89+
<div className="w-96 rounded-lg bg-gray-800 p-6 shadow-lg">
90+
<h2 className="mb-4 text-xl font-semibold">Add New Resource</h2>
91+
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
92+
<input
93+
type="text"
94+
placeholder="Title"
95+
value={newResource.title}
96+
onChange={(e) => setNewResource({ ...newResource, title: e.target.value })}
97+
className="rounded bg-gray-700 p-2 text-white"
98+
required
99+
/>
100+
<textarea
101+
placeholder="Description"
102+
value={newResource.description}
103+
onChange={(e) => setNewResource({ ...newResource, description: e.target.value })}
104+
className="rounded bg-gray-700 p-2 text-white"
105+
required
106+
></textarea>
107+
<input
108+
type="url"
109+
placeholder="Resource Link"
110+
value={newResource.link}
111+
onChange={(e) => setNewResource({ ...newResource, link: e.target.value })}
112+
className="rounded bg-gray-700 p-2 text-white"
113+
required
114+
/>
115+
<input
116+
type="text"
117+
placeholder="Tags (comma separated)"
118+
value={newResource.tags}
119+
onChange={(e) => setNewResource({ ...newResource, tags: e.target.value })}
120+
className="rounded bg-gray-700 p-2 text-white"
121+
required
122+
/>
123+
<div className="flex justify-end gap-2">
124+
<button
125+
type="button"
126+
onClick={() => setIsModalOpen(false)}
127+
className="rounded bg-gray-600 p-2 text-white hover:bg-gray-500"
128+
>
129+
Cancel
130+
</button>
131+
<button type="submit" className="rounded bg-[#00a6fb] p-2 text-white hover:bg-[#0096e6]">
132+
Submit
133+
</button>
134+
</div>
135+
</form>
136+
</div>
137+
</div>
138+
)}
139+
140+
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
141+
{filteredResources.map((resource, index) => (
142+
<div key={index} className="rounded-lg border border-gray-700 bg-gray-800 p-5 shadow-lg">
143+
<h2 className="text-xl font-semibold">{resource.title}</h2>
144+
<p className="mt-2 text-sm text-gray-400">{resource.description}</p>
145+
<div className="mt-2 flex flex-wrap gap-2">
146+
{resource.tags.map((tag, idx) => (
147+
<span key={idx} className="rounded-full bg-gray-700 px-2 py-1 text-xs">
148+
{tag}
149+
</span>
150+
))}
151+
</div>
152+
<div className="mt-4 flex items-center justify-between">
153+
<a
154+
href={resource.link}
155+
target="_blank"
156+
rel="noopener noreferrer"
157+
className="flex items-center gap-2 text-[#00a6fb] hover:underline"
158+
>
159+
<FaLink /> Visit Resource
160+
</a>
161+
<div className="flex items-center space-x-4">
162+
<button
163+
onClick={() => handleVote(index, 'upvote')}
164+
className={`flex items-center gap-1 ${userVotes[index] === 'upvote' ? 'text-green-400' : 'text-gray-400'}`}
165+
>
166+
<FaThumbsUp /> {resource.upvotes}
167+
</button>
168+
<button
169+
onClick={() => handleVote(index, 'downvote')}
170+
className={`flex items-center gap-1 ${userVotes[index] === 'downvote' ? 'text-red-400' : 'text-gray-400'}`}
171+
>
172+
<FaThumbsDown /> {resource.downvotes}
173+
</button>
174+
</div>
175+
</div>
176+
</div>
177+
))}
178+
</div>
179+
</div>
180+
);
181+
};
182+
183+
export default ResourceSharingHub;

src/Page/Resources.jsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,12 @@ const ResourcesCards = () => {
391391
link: '/CodingPlatforms',
392392
tags: ['Coding Platforms', 'Coding', 'Challenges', 'Competitive Programming'],
393393
},
394+
{
395+
title: 'Resource Sharing Hub',
396+
description: 'Share and explore valuable resources for developers',
397+
link: '/ResourceSharing',
398+
tags: ['sharing', 'resources'],
399+
},
394400
];
395401

396402
const filteredResources = resources.filter((resources) => {

0 commit comments

Comments
 (0)