move all components that are exclusive to one page
This commit is contained in:
@@ -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;
|
||||
Reference in New Issue
Block a user