move all components that are exclusive to one page

This commit is contained in:
2026-04-23 15:37:39 +02:00
parent 23233d4be6
commit d34eb5714f
17 changed files with 19 additions and 19 deletions
+12
View File
@@ -0,0 +1,12 @@
import Projects from "../../components/Projects/Projects.jsx";
import Footer from "../../components/Footer/Footer.jsx";
function ProjectsPage() {
return (
<div>
<Projects/>
<Footer/>
</div>
);
}
export default ProjectsPage;
@@ -0,0 +1,134 @@
.filter-btn {
position: absolute;
top: 2em;
right: 20px;
border: none;
background: none;
padding: 14px;
cursor: pointer;
z-index: 10;
transition: background-color 0.3s ease;
}
.filter-btn svg {
color: var(--text-color);
transition: all 0.3s ease;
}
.filter-btn svg:hover {
color: var(--title-color);
transform: translateY(-2px);
}
.filter-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(5px);
z-index: 100;
}
.filter-sidebar {
position: fixed;
top: 0;
right: -100%;
width: 100%;
max-width: 350px;
height: 100%;
background: rgba(42, 42, 42, 0.5);
backdrop-filter: blur(15px);
border-left: solid rgba(100,100,100,0.5) 1px;
z-index: 101;
display: flex;
flex-direction: column;
transition: right 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.filter-sidebar.visible {
right: 0;
}
.filter-sidebar-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 1.5rem;
border-bottom: 1px solid rgba(100, 100, 100, 0.5);
}
.close-filter-btn {
background: none;
border: none;
color: var(--text-color);
cursor: pointer;
padding: 5px;
display: flex;
align-items: center;
justify-content: center;
transition: color 0.3s ease;
}
.close-filter-btn:hover {
color: var(--title-color);
}
.filter-sidebar-content {
padding: 1.5rem;
overflow-y: auto;
}
.filter-sidebar-content h4 {
color: var(--text-color);
font-size: 0.9rem;
text-transform: uppercase;
letter-spacing: 1px;
margin-top: 0;
margin-bottom: 1rem;
display: flex;
justify-content: start;
}
.filter-group {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
margin-bottom: 2rem;
}
.filter-tag {
background-color: transparent;
border: 1px solid var(--text-color);
color: var(--text-color);
padding: 8px 16px;
border-radius: 20px;
cursor: pointer;
font-size: 0.9rem;
font-weight: 600;
text-decoration: none;
transition: all 0.3s ease;
}
.filter-tag:hover {
background-color: var(--text-color);
color: #1E1E1E;
transform: translateY(-2px);
}
.filter-tag.active {
background-color: rgba(217, 95, 70, 0.3);
color: var(--important-color);
border-color: var(--important-color);
}
@media (max-width: 480px) {
.filter-btn {
top: 5em;
right: 20px;
}
}
@@ -0,0 +1,154 @@
import './Filter.css';
import React, {useEffect, useState} from "react";
function Filter({ filters, setFilters }) {
const [isFilterVisible, setIsFilterVisible] = useState(false);
const showFilters = () => setIsFilterVisible(true);
const hideFilters = () => setIsFilterVisible(false);
const [skills, setSkills] = useState([]);
const [error, setError] = useState(null);
const handleCategoryFilter = (category) => {
setFilters(prev => {
const alreadySelected = prev.category.includes(category);
return {
...prev,
category: alreadySelected
? prev.category.filter(c => c !== category)
: [...prev.category, category],
};
});
};
const toggleTechnologyFilter = (tech) => {
setFilters(prev => {
const alreadySelected = prev.technology.includes(tech);
return {
...prev,
technology: alreadySelected
? prev.technology.filter(t => t !== tech)
: [...prev.technology, tech],
};
});
};
const setYearOrder = (order) => {
setFilters(prev => ({
...prev,
yearOrder: prev.yearOrder === order ? null : order
}));
};
const resetFilters = () => {
setFilters({
category: [],
technology: [],
yearOrder: filters.yearOrder,
});
};
useEffect(() => {
if (isFilterVisible) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = '';
}
return () => {
document.body.style.overflow = '';
};
}, [isFilterVisible]);
useEffect(() => {
const fetchSkills = async () => {
try {
const response = await fetch('/api/skills/');
if (!response.ok) {
throw new Error(`Erreur HTTP: ${response.status}`);
}
const data = await response.json();
setSkills(data.data);
} catch (err) {
setError(err.message);
}
};
fetchSkills();
}, []);
if (error) {
return <div>Error retrieving data: {error}</div>;
}
return (
<div>
<button className="filter-btn" onClick={showFilters}>
<svg xmlns="http://www.w3.org/2000/svg" height="30" viewBox="0 -960 960 960" width="30" fill="currentColor">
<path d="M440-160q-17 0-28.5-11.5T400-200v-240L168-736q-15-20-4.5-42t36.5-22h560q26 0 36.5 22t-4.5 42L560-440v240q0 17-11.5 28.5T520-160h-80Zm40-308 198-252H282l198 252Zm0 0Z"/>
</svg>
</button>
{isFilterVisible ? <div className="filter-overlay" onClick={hideFilters}></div> : null}
<div className={`filter-sidebar ${isFilterVisible ? 'visible' : ''}`}>
<div className="filter-sidebar-header">
<h3>Filter Projects</h3>
<button className="close-filter-btn" onClick={hideFilters}>
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="currentColor">
<path d="m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z"/>
</svg>
</button>
</div>
<div className="filter-sidebar-content">
<div className="filter-group">
<button
className={`filter-tag ${filters.category.length === 0 && filters.technology.length === 0 ? "active" : ""}`}
onClick={resetFilters}
>All</button>
</div>
<h4>Category</h4>
<div className="filter-group">
<button
className={`filter-tag ${filters.category.includes("school") ? "active" : ""}`}
onClick={() => handleCategoryFilter("school")}
>School</button>
<button
className={`filter-tag ${filters.category.includes("personal") ? "active" : ""}`}
onClick={() => handleCategoryFilter("personal")}
>Personal</button>
</div>
<h4>Technology</h4>
<div className="filter-group">
{skills.map(skill => (
<button
className={`filter-tag ${filters.technology.includes(skill.name) ? "active" : ""}`}
onClick={() => toggleTechnologyFilter(skill.name)}
>{skill.name}</button>
))}
</div>
<h4>YEAR</h4>
<div className="filter-group">
<button
className={`filter-tag ${filters.yearOrder === "asc" ? "active" : ""}`}
onClick={() => setYearOrder("asc")}
>Ascending</button>
<button
className={`filter-tag ${filters.yearOrder === "desc" ? "active" : ""}`}
onClick={() => setYearOrder("desc")}
>Descending</button>
</div>
</div>
</div>
</div>
);
}
export default Filter;