Skip to content

Commit ef88a8b

Browse files
authored
Merge pull request #91 from Jils31/master
Added Tech Stack based filters
2 parents 3569eca + 927d8f2 commit ef88a8b

File tree

3 files changed

+175
-87
lines changed

3 files changed

+175
-87
lines changed

index.html

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,14 @@ <h2><i class="fas fa-filter"></i> Filter Projects</h2>
7272
<input type="checkbox" id="has-demo">
7373
<label for="has-demo">Has Open Issues</label>
7474
</div>
75-
<div class="filter-group">
76-
<input type="checkbox" id="has-readme">
77-
<label for="has-readme">Has README</label>
78-
</div>
7975
<button class="btn-secondary" id="apply-filters">Apply Filters</button>
8076
<button class="btn-secondary" id="reset-filters">Reset</button>
77+
<div class="filter-group">
78+
<label>Filter by Tech Stack:</label>
79+
<div id="tag-filters" class="tag-filters">
80+
<!-- Dynamic tags will be appeared here -->
81+
</div>
82+
</div>
8183
</div>
8284
</div>
8385
</section>

script.js

Lines changed: 129 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const sampleProjects = [
2020
description: 'A responsive weather application with beautiful animations and detailed forecasts. Features location-based weather data and interactive charts.',
2121
repoUrl: 'https://github.com/example/weather-dashboard',
2222
demoUrl: 'https://example.github.io/weather-dashboard/',
23-
difficulty: 'Intermediate',
23+
difficulty: 'intermediate',
2424
upvotes: 28,
2525
hasDemo: true,
2626
hasReadme: true,
@@ -59,7 +59,7 @@ const sampleProjects = [
5959
description: 'A simple and intuitive expense tracker app to monitor daily spending, manage budgets, and gain financial insights.',
6060
repoUrl: 'https://github.com/DineshPabboju/Expense-Tracker-App',
6161
demoUrl: 'https://expense-tracker-app-04.netlify.app/',
62-
difficulty: 'Intermediate',
62+
difficulty: 'intermediate',
6363
upvotes: 21,
6464
hasDemo: true,
6565
hasReadme: false,
@@ -97,25 +97,43 @@ const sampleProjects = [
9797

9898
// Store the current projects array
9999
let currentProjects = [...sampleProjects];
100+
let selectedTag = null;
101+
102+
//Store all the unique tags
103+
const allTagSet = new Set();
104+
sampleProjects.forEach(project => {
105+
project.tags.forEach(tag => allTagSet.add(tag));
106+
})
107+
108+
const uniqueTags = Array.from(allTagSet);
100109

101110
// DOM elements
102111
const projectsContainer = document.getElementById('projects-container');
103112
const loadingElement = document.getElementById('loading');
104113
const emptyStateElement = document.getElementById('empty-state');
105114
const difficultyFilter = document.getElementById('difficulty');
106115
const hasDemoFilter = document.getElementById('has-demo');
107-
const hasReadmeFilter = document.getElementById('has-readme');
108116
const applyFiltersBtn = document.getElementById('apply-filters');
109117
const resetFiltersBtn = document.getElementById('reset-filters');
110118
const searchInput = document.getElementById('search-input');
111119
const clearSearchBtn = document.getElementById('clear-search');
120+
const tagFiltersContainer = document.querySelector('.tag-filters');
121+
122+
uniqueTags.forEach(tag => {
123+
const button = document.createElement('button');
124+
button.textContent = tag;
125+
button.classList.add('tag-filter-btn')
126+
button.dataset.tag = tag
127+
tagFiltersContainer.appendChild(button)
128+
})
112129

113130
// Initialize the app
114131
function init() {
115132
setTimeout(() => {
116133
hideLoading();
117134
renderProjects(currentProjects);
118135
setupEventListeners();
136+
initializeTagFilterListener();
119137
}, 1000); // Simulate loading time
120138
}
121139

@@ -143,6 +161,28 @@ const sampleProjects = [
143161
});
144162
}
145163

164+
function initializeTagFilterListener() {
165+
tagFiltersContainer.addEventListener('click', (e) => {
166+
if (e.target.classList.contains('tag-filter-btn')) {
167+
const clickedTag = e.target.dataset.tag;
168+
169+
// Toggle selection
170+
if (selectedTag === clickedTag) {
171+
selectedTag = null;
172+
e.target.classList.remove('active');
173+
} else {
174+
selectedTag = clickedTag;
175+
// Remove active from all buttons
176+
document.querySelectorAll('.tag-filter-btn').forEach(btn => btn.classList.remove('active'));
177+
e.target.classList.add('active');
178+
}
179+
180+
applyFilters(); // Re-apply filters based on tag
181+
}
182+
});
183+
}
184+
185+
146186
// Render projects
147187
function renderProjects(projects) {
148188
if (projects.length === 0) {
@@ -185,6 +225,12 @@ const sampleProjects = [
185225
: ' • <i class="fas fa-exclamation-triangle meta-icon"></i> No README'
186226
}
187227
</div>
228+
229+
<div class="project-tags">
230+
${project.tags.map((item, index)=>`
231+
<span class="tag-badge">${item}</span>
232+
`).join('')}
233+
</div>
188234
189235
<div class="upvote-section">
190236
${project.hasDemo && project.demoUrl
@@ -231,7 +277,6 @@ const sampleProjects = [
231277

232278
const difficulty = difficultyFilter.value;
233279
const needsDemo = hasDemoFilter.checked;
234-
const needsReadme = hasReadmeFilter.checked;
235280
const searchTerm = searchInput.value.toLowerCase().trim();
236281

237282
// Apply search filter
@@ -252,8 +297,8 @@ const sampleProjects = [
252297
filtered = filtered.filter(p => p.hasDemo);
253298
}
254299

255-
if (needsReadme) {
256-
filtered = filtered.filter(p => p.hasReadme);
300+
if(selectedTag){
301+
filtered = filtered.filter(project => project.tags.includes(selectedTag));
257302
}
258303

259304
return filtered;
@@ -293,88 +338,89 @@ const sampleProjects = [
293338
function resetFilters() {
294339
difficultyFilter.value = 'all';
295340
hasDemoFilter.checked = false;
296-
hasReadmeFilter.checked = false;
297341
searchInput.value = '';
298342
clearSearchBtn.style.display = 'none';
343+
selectedTag = null;
344+
document.querySelectorAll('.tag-filter-btn').forEach(btn => btn.classList.remove('active'));
299345
renderProjects(sampleProjects);
300346
}
301347

302348
// Make handleUpvote globally available
303349
window.handleUpvote = handleUpvote;
304350

305-
// Start the app
306-
document.addEventListener("DOMContentLoaded", init);
307-
// Adding m own version and also added a feature where the input field will get clear on clicking the send message button
308-
function validateForm() {
309-
const name = document.getElementById("name").value.trim();
310-
const lastname = document.getElementById("lastname").value.trim();
311-
const email = document.getElementById("email").value.trim();
312-
const message = document.getElementById("message").value.trim();
313-
if (!name || !lastname || !email || !message) {
314-
alert("Please fill in all fields.");
315-
return false;
316-
}
317-
const emailPattern = /^[^ ]+@[^ ]+\.[a-z]{2,3}$/;
318-
if (!email.match(emailPattern)) {
319-
alert("Please enter a valid email.");
320-
return false;
321-
}
322-
323-
// Show the overlay
324-
const overlay = document.getElementById("message-overlay");
325-
overlay.style.opacity = "1";
326-
overlay.style.pointerEvents = "auto";
327-
328-
// Hide the overlay after 3 seconds
329-
setTimeout(() => {
330-
overlay.style.opacity = "0";
331-
overlay.style.pointerEvents = "none";
332-
}, 3000);
333-
334-
// Clear form
335-
document.getElementById("contact-form").reset();
336-
337-
return false; // Prevent actual form submission
338-
}
339-
340-
const toggle = document.getElementById('darkModeToggle');
341-
const body = document.body;
342-
const icon = document.getElementById('themeIcon');
343-
344-
// Load preference
345-
const savedTheme = localStorage.getItem('theme');
346-
if (savedTheme === 'dark') {
347-
body.classList.add('dark-theme');
348-
icon.textContent = '☀️'; // Sun in dark mode
349-
} else {
350-
icon.textContent = '🌙'; // Moon in light mode
351-
}
352-
353-
toggle.addEventListener('click', () => {
354-
body.classList.toggle('dark-theme');
355-
const theme = body.classList.contains('dark-theme') ? 'dark' : 'light';
356-
localStorage.setItem('theme', theme);
357-
358-
// Update icon
359-
icon.textContent = theme === 'dark' ? '☀️' : '🌙';
360-
});
361-
362-
//Review Section JS
363-
const swiper = new Swiper(".review-swiper", {
364-
loop: true,
365-
slidesPerView: 1,
366-
spaceBetween: 20,
367-
navigation: {
368-
nextEl: ".swiper-button-next",
369-
prevEl: ".swiper-button-prev",
370-
},
371-
keyboard: {
372-
enabled: true,
373-
},
374-
mousewheel: {
375-
forceToAxis: true,
376-
},
377-
grabCursor: true,
378-
speed: 600,
379-
});
380-
351+
// Start the app
352+
document.addEventListener("DOMContentLoaded", init);
353+
// Adding m own version and also added a feature where the input field will get clear on clicking the send message button
354+
function validateForm() {
355+
const name = document.getElementById("name").value.trim();
356+
const lastname = document.getElementById("lastname").value.trim();
357+
const email = document.getElementById("email").value.trim();
358+
const message = document.getElementById("message").value.trim();
359+
if (!name || !lastname || !email || !message) {
360+
alert("Please fill in all fields.");
361+
return false;
362+
}
363+
const emailPattern = /^[^ ]+@[^ ]+\.[a-z]{2,3}$/;
364+
if (!email.match(emailPattern)) {
365+
alert("Please enter a valid email.");
366+
return false;
367+
}
368+
369+
// Show the overlay
370+
const overlay = document.getElementById("message-overlay");
371+
overlay.style.opacity = "1";
372+
overlay.style.pointerEvents = "auto";
373+
374+
// Hide the overlay after 3 seconds
375+
setTimeout(() => {
376+
overlay.style.opacity = "0";
377+
overlay.style.pointerEvents = "none";
378+
}, 3000);
379+
380+
// Clear form
381+
document.getElementById("contact-form").reset();
382+
383+
return false; // Prevent actual form submission
384+
}
385+
386+
const toggle = document.getElementById('darkModeToggle');
387+
const body = document.body;
388+
const icon = document.getElementById('themeIcon');
389+
390+
// Load preference
391+
const savedTheme = localStorage.getItem('theme');
392+
if (savedTheme === 'dark') {
393+
body.classList.add('dark-theme');
394+
icon.textContent = '☀️'; // Sun in dark mode
395+
} else {
396+
icon.textContent = '🌙'; // Moon in light mode
397+
}
398+
399+
toggle.addEventListener('click', () => {
400+
body.classList.toggle('dark-theme');
401+
const theme = body.classList.contains('dark-theme') ? 'dark' : 'light';
402+
localStorage.setItem('theme', theme);
403+
404+
// Update icon
405+
icon.textContent = theme === 'dark' ? '☀️' : '🌙';
406+
});
407+
408+
//Review Section JS
409+
const swiper = new Swiper(".review-swiper", {
410+
loop: true,
411+
slidesPerView: 1,
412+
spaceBetween: 20,
413+
navigation: {
414+
nextEl: ".swiper-button-next",
415+
prevEl: ".swiper-button-prev",
416+
},
417+
keyboard: {
418+
enabled: true,
419+
},
420+
mousewheel: {
421+
forceToAxis: true,
422+
},
423+
grabCursor: true,
424+
speed: 600,
425+
});
426+

style.css

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,28 @@
332332
transform: translateY(-2px);
333333
}
334334

335+
.tag-filters {
336+
display: flex;
337+
flex-wrap: wrap;
338+
gap: 8px;
339+
margin: 10px 0;
340+
}
341+
342+
.tag-filter-btn {
343+
padding: 6px 12px;
344+
background: #f0f0f0;
345+
border: 1px solid #ccc;
346+
border-radius: 20px;
347+
cursor: pointer;
348+
transition: background 0.3s;
349+
}
350+
351+
.tag-filter-btn.active {
352+
background: #007bff;
353+
color: #fff;
354+
border-color: #007bff;
355+
}
356+
335357
/* Projects section */
336358
.projects-section {
337359
padding: 2rem 0;
@@ -462,6 +484,24 @@
462484
color: #667eea;
463485
}
464486

487+
.project-tags {
488+
margin-top: 0.5rem;
489+
margin-bottom: 5px;
490+
display: flex;
491+
flex-wrap: wrap;
492+
gap: 6px;
493+
}
494+
495+
.tag-badge {
496+
background-color: #e0e0e0;
497+
color: #333;
498+
padding: 4px 8px;
499+
border-radius: 12px;
500+
font-size: 12px;
501+
display: inline-block;
502+
white-space: nowrap;
503+
}
504+
465505
.upvote-section {
466506
display: flex;
467507
justify-content: space-between;

0 commit comments

Comments
 (0)