Skip to content

Commit af8029b

Browse files
authored
Merge branch 'Varshitha713:master' into validation
2 parents cae46eb + 319c186 commit af8029b

File tree

3 files changed

+370
-17
lines changed

3 files changed

+370
-17
lines changed

index.html

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,15 @@ <h2><i class="fas fa-filter"></i> Filter Projects</h2>
5555
<button class="clear-search" id="clear-search" style="display: none;"><i class="fas fa-times"></i></button>
5656
</div>
5757
</div>
58+
<div class="filter-group">
59+
<label for="sort-by">Sort by:</label>
60+
<select id="sort-by">
61+
<option value="popularity">Most Popular</option>
62+
<option value="newest">Newest First</option>
63+
<option value="difficulty">Difficulty</option>
64+
<option value="alphabetical">A-Z</option>
65+
</select>
66+
</div>
5867
<div class="filter-group">
5968
<label for="difficulty">Difficulty:</label>
6069
<select id="difficulty">
@@ -184,6 +193,31 @@ <h2 class="section-title" style="font-size: 3rem; text-align: center; margin-bot
184193
</main>
185194

186195
<footer class="footer">
196+
<div class="footer-flex-container">
197+
<div class="footer-1">
198+
<div class="footer-logo">CodeCanvas</div>
199+
<ul class="socials">
200+
<li><a href="https://github.com/Varshitha713/CodeCanvas" target="_blank"><i class="fa-brands fa-github"></i></a></li>
201+
<li><a href="https://www.linkedin.com/in/varshitha-macha/" target="_blank"><i class="fa-brands fa-linkedin"></i></a></li>
202+
<li><a href="https://discord.com/users/1179807221329182780" target="_blank"><i class="fa-brands fa-discord"></i></a></li>
203+
<li><a href="#"><i class="fa-solid fa-envelope"></i></a></li>
204+
</ul>
205+
</div>
206+
<div class="quick-links">
207+
<ul>
208+
<div class="p-1">
209+
<li><a href="#">Home</a></li>
210+
<li><a href="#browse">Browse</a></li>
211+
<li><a href="#submit">Submit</a></li>
212+
</div>
213+
<div class="p-2">
214+
<li><a href="#about">About</a></li>
215+
<li><a href="#reviews">Reviews</a></li>
216+
<li><a href="#contact">Contact Us</a></li>
217+
</div>
218+
</ul>
219+
</div>
220+
</div>
187221
<div class="container">
188222
<p>&copy; 2025 CodeCanvas. Open source project for the developer community.</p>
189223
<p>Built with ❤️ for showcasing amazing front-end projects</p>

script.js

Lines changed: 178 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,86 @@ const sampleProjects = [
9999
let currentProjects = [...sampleProjects];
100100
let selectedTag = null;
101101

102+
// Voting system
103+
class VotingSystem {
104+
constructor() {
105+
this.userFingerprint = this.generateUserFingerprint();
106+
this.votes = this.loadVotes();
107+
this.initializeProjectVotes();
108+
}
109+
110+
generateUserFingerprint() {
111+
let fingerprint = localStorage.getItem('userFingerprint');
112+
if (!fingerprint) {
113+
fingerprint = 'user_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
114+
localStorage.setItem('userFingerprint', fingerprint);
115+
}
116+
return fingerprint;
117+
}
118+
119+
loadVotes() {
120+
const savedVotes = localStorage.getItem('projectVotes');
121+
return savedVotes ? JSON.parse(savedVotes) : {};
122+
}
123+
124+
saveVotes() {
125+
localStorage.setItem('projectVotes', JSON.stringify(this.votes));
126+
}
127+
128+
initializeProjectVotes() {
129+
sampleProjects.forEach(project => {
130+
if (!this.votes[project.id]) {
131+
this.votes[project.id] = {
132+
count: project.upvotes || 0,
133+
voters: []
134+
};
135+
}
136+
});
137+
this.saveVotes();
138+
}
139+
140+
canUserVote(projectId) {
141+
const projectVotes = this.votes[projectId];
142+
return projectVotes && !projectVotes.voters.includes(this.userFingerprint);
143+
}
144+
145+
upvoteProject(projectId) {
146+
if (!this.canUserVote(projectId)) {
147+
return { success: false, message: 'You have already voted for this project!' };
148+
}
149+
150+
this.votes[projectId].count++;
151+
this.votes[projectId].voters.push(this.userFingerprint);
152+
this.saveVotes();
153+
154+
// Update the project in currentProjects array
155+
const project = currentProjects.find(p => p.id === projectId);
156+
if (project) {
157+
project.upvotes = this.votes[projectId].count;
158+
}
159+
160+
// Update the project in sampleProjects array
161+
const sampleProject = sampleProjects.find(p => p.id === projectId);
162+
if (sampleProject) {
163+
sampleProject.upvotes = this.votes[projectId].count;
164+
}
165+
166+
return { success: true, newCount: this.votes[projectId].count };
167+
}
168+
169+
getProjectVotes(projectId) {
170+
return this.votes[projectId] ? this.votes[projectId].count : 0;
171+
}
172+
173+
hasUserVoted(projectId) {
174+
const projectVotes = this.votes[projectId];
175+
return projectVotes && projectVotes.voters.includes(this.userFingerprint);
176+
}
177+
}
178+
179+
// Initialize voting system
180+
const votingSystem = new VotingSystem();
181+
102182
//Store all the unique tags
103183
const allTagSet = new Set();
104184
sampleProjects.forEach(project => {
@@ -111,6 +191,7 @@ const sampleProjects = [
111191
const projectsContainer = document.getElementById('projects-container');
112192
const loadingElement = document.getElementById('loading');
113193
const emptyStateElement = document.getElementById('empty-state');
194+
const sortByFilter = document.getElementById('sort-by');
114195
const difficultyFilter = document.getElementById('difficulty');
115196
const hasDemoFilter = document.getElementById('has-demo');
116197
const applyFiltersBtn = document.getElementById('apply-filters');
@@ -147,6 +228,7 @@ const sampleProjects = [
147228
function setupEventListeners() {
148229
applyFiltersBtn.addEventListener('click', applyFilters);
149230
resetFiltersBtn.addEventListener('click', resetFilters);
231+
sortByFilter.addEventListener('change', applyFilters);
150232

151233
// Search functionality
152234
searchInput.addEventListener('input', handleSearch);
@@ -191,11 +273,43 @@ const sampleProjects = [
191273
return;
192274
}
193275

276+
// Sort projects based on selected option
277+
const sortBy = sortByFilter.value;
278+
const sortedProjects = [...projects].sort((a, b) => {
279+
switch (sortBy) {
280+
case 'popularity':
281+
const aVotes = votingSystem.getProjectVotes(a.id);
282+
const bVotes = votingSystem.getProjectVotes(b.id);
283+
return bVotes - aVotes;
284+
285+
case 'newest':
286+
// Since we don't have dates, sort by ID (assuming higher ID = newer)
287+
return b.id - a.id;
288+
289+
case 'difficulty':
290+
const difficultyOrder = { 'beginner': 1, 'intermediate': 2, 'advanced': 3 };
291+
return difficultyOrder[a.difficulty] - difficultyOrder[b.difficulty];
292+
293+
case 'alphabetical':
294+
return a.title.localeCompare(b.title);
295+
296+
default:
297+
return 0;
298+
}
299+
});
300+
194301
emptyStateElement.style.display = 'none';
195302
projectsContainer.style.display = 'grid';
196303

197-
projectsContainer.innerHTML = projects.map(project => `
198-
<div class="project-card">
304+
projectsContainer.innerHTML = sortedProjects.map((project, index) => {
305+
const hasVoted = votingSystem.hasUserVoted(project.id);
306+
const canVote = votingSystem.canUserVote(project.id);
307+
const voteCount = votingSystem.getProjectVotes(project.id);
308+
const isTopRanked = sortBy === 'popularity' && index < 3 && voteCount > 0;
309+
310+
return `
311+
<div class="project-card ${isTopRanked ? 'top-ranked' : ''}">
312+
${isTopRanked ? `<div class="rank-badge">#${index + 1}</div>` : ''}
199313
${project.previewImage
200314
? `<img src="${project.previewImage}" alt="${project.title}" class="project-image"
201315
onerror="this.outerHTML='<div class=\\'project-placeholder\\'>No Preview Available</div>'">`
@@ -239,32 +353,80 @@ const sampleProjects = [
239353
</a>`
240354
: '<span></span>'
241355
}
242-
<button class="upvote-btn" onclick="handleUpvote(${project.id})">
356+
<button class="upvote-btn ${hasVoted ? 'voted' : ''}"
357+
onclick="handleUpvote(${project.id})"
358+
${!canVote ? 'disabled' : ''}
359+
title="${hasVoted ? 'You have already voted for this project' : 'Click to upvote this project'}">
243360
<i class="fas fa-arrow-up"></i>
244-
<span>${project.upvotes}</span>
361+
<span>${voteCount}</span>
245362
</button>
246363
</div>
247364
</div>
248-
`).join('');
365+
`;
366+
}).join('');
249367
}
250368

251369
// Handle upvote
252370
function handleUpvote(projectId) {
253-
const project = currentProjects.find(p => p.id === projectId);
254-
if (project) {
255-
project.upvotes++;
256-
// Re-render projects to update the upvote count
257-
renderProjects(applyCurrentFilters());
258-
259-
// Add visual feedback
260-
const button = event.target.closest('.upvote-btn');
261-
button.style.transform = 'scale(1.1)';
371+
const result = votingSystem.upvoteProject(projectId);
372+
373+
if (!result.success) {
374+
// Show error message
375+
showNotification(result.message, 'error');
376+
return;
377+
}
378+
379+
// Show success message
380+
showNotification('Vote added successfully!', 'success');
381+
382+
// Re-render projects to update the upvote count and sorting
383+
renderProjects(applyCurrentFilters());
384+
385+
// Add visual feedback
386+
const button = event.target.closest('.upvote-btn');
387+
if (button) {
388+
button.style.transform = 'scale(1.2)';
262389
setTimeout(() => {
263390
button.style.transform = 'scale(1)';
264-
}, 150);
391+
}, 200);
265392
}
266393
}
267394

395+
// Show notification function
396+
function showNotification(message, type = 'info') {
397+
// Remove existing notifications
398+
const existingNotification = document.querySelector('.vote-notification');
399+
if (existingNotification) {
400+
existingNotification.remove();
401+
}
402+
403+
// Create notification element
404+
const notification = document.createElement('div');
405+
notification.className = `vote-notification ${type}`;
406+
notification.innerHTML = `
407+
<i class="fas ${type === 'success' ? 'fa-check-circle' : 'fa-exclamation-circle'}"></i>
408+
<span>${message}</span>
409+
`;
410+
411+
// Add to body
412+
document.body.appendChild(notification);
413+
414+
// Show notification
415+
setTimeout(() => {
416+
notification.classList.add('show');
417+
}, 10);
418+
419+
// Hide notification after 3 seconds
420+
setTimeout(() => {
421+
notification.classList.remove('show');
422+
setTimeout(() => {
423+
if (notification.parentNode) {
424+
notification.remove();
425+
}
426+
}, 300);
427+
}, 3000);
428+
}
429+
268430
// Apply filters
269431
function applyFilters() {
270432
const filteredProjects = applyCurrentFilters();
@@ -336,6 +498,7 @@ const sampleProjects = [
336498

337499
// Reset filters
338500
function resetFilters() {
501+
sortByFilter.value = 'popularity';
339502
difficultyFilter.value = 'all';
340503
hasDemoFilter.checked = false;
341504
searchInput.value = '';

0 commit comments

Comments
 (0)