add of projects page, and new projects
This commit is contained in:
parent
78dec2a053
commit
b17d88f869
@ -1,10 +1,14 @@
|
|||||||
# To do
|
# To do
|
||||||
|
|
||||||
- Faire une page pour lister tous les projets
|
- Régler problème de scoll entre page
|
||||||
- Rafaire l'organisation du projet
|
- Rafaire l'organisation du projet
|
||||||
|
- Rajouter les images manquantes des projects
|
||||||
- Faire une page explicative par projet
|
- Faire une page explicative par projet
|
||||||
- Faire du responsive
|
- Faire du responsive
|
||||||
|
|
||||||
|
## Plus tard
|
||||||
- Refaire la section des skills
|
- Refaire la section des skills
|
||||||
|
- Ajouter un fonction de filtrage des projects (en fonction de la date, des skills)
|
||||||
|
|
||||||
|
|
||||||
## Usefull commands
|
## Usefull commands
|
||||||
|
|||||||
BIN
public/assets/images/empty/empty_1.png
Normal file
BIN
public/assets/images/empty/empty_1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 79 KiB |
@ -8,7 +8,7 @@ function App() {
|
|||||||
return (
|
return (
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<HomePage />} />
|
<Route path="/" element={<HomePage />} />
|
||||||
<Route path="/projets" element={<ProjectsPage />} />
|
<Route path="/projects" element={<ProjectsPage />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,7 +31,7 @@ function Experiences() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <div>Erreur lors de la récupération des données : {error}</div>;
|
return <div>Error retrieving data: {error}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,44 +1,51 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
import "../styles/NavBar.css";
|
import "../styles/NavBar.css";
|
||||||
|
|
||||||
const NavBar = () => {
|
const NavBar = () => {
|
||||||
const [active, setActive] = useState("home-section");
|
const [active, setActive] = useState("home-section");
|
||||||
const [isScrolling, setIsScrolling] = useState(false);
|
const [isScrolling, setIsScrolling] = useState(false);
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const sections = document.querySelectorAll("section");
|
if (location.pathname === '/') {
|
||||||
const navHeight = document.querySelector(".navbar")?.offsetHeight + 40 || 120;
|
const sections = document.querySelectorAll("section");
|
||||||
|
const navHeight = document.querySelector(".navbar")?.offsetHeight + 40 || 120;
|
||||||
|
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
if (isScrolling) return;
|
if (isScrolling) return;
|
||||||
|
|
||||||
let current = "home-section";
|
let current = "home-section";
|
||||||
sections.forEach((section) => {
|
sections.forEach((section) => {
|
||||||
const sectionTop = section.offsetTop - navHeight;
|
const sectionTop = section.offsetTop - navHeight;
|
||||||
if (window.scrollY >= sectionTop) {
|
if (window.scrollY >= sectionTop) {
|
||||||
current = section.id;
|
current = section.id;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
setActive(current);
|
||||||
|
};
|
||||||
|
|
||||||
setActive(current);
|
window.addEventListener("scroll", handleScroll);
|
||||||
};
|
handleScroll();
|
||||||
|
|
||||||
window.addEventListener("scroll", handleScroll);
|
return () => {
|
||||||
handleScroll();
|
window.removeEventListener("scroll", handleScroll);
|
||||||
|
};
|
||||||
return () => {
|
}
|
||||||
window.removeEventListener("scroll", handleScroll);
|
}, [isScrolling, location.pathname]);
|
||||||
};
|
|
||||||
}, [isScrolling]);
|
|
||||||
|
|
||||||
const handleClick = (id) => {
|
const handleClick = (id) => {
|
||||||
setActive(id);
|
if (location.pathname === '/') {
|
||||||
setIsScrolling(true);
|
setActive(id);
|
||||||
document.getElementById(id)?.scrollIntoView({ behavior: "smooth" });
|
setIsScrolling(true);
|
||||||
setTimeout(() => setIsScrolling(false), 800);
|
document.getElementById(id)?.scrollIntoView({ behavior: "smooth" });
|
||||||
|
setTimeout(() => setIsScrolling(false), 800);
|
||||||
|
} else {
|
||||||
|
navigate(`/#${id}`);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="navbar">
|
<nav className="navbar">
|
||||||
<ul className="nav-list">
|
<ul className="nav-list">
|
||||||
@ -79,4 +86,4 @@ const NavBar = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default NavBar;
|
export default NavBar;
|
||||||
@ -1,10 +1,13 @@
|
|||||||
import SingleProject from "./SingleProject.jsx";
|
import SingleProject from "./SingleProject.jsx";
|
||||||
import "../styles/Projects.css"
|
import "../styles/Projects.css"
|
||||||
import React, {useEffect, useState} from "react";
|
import React, {useEffect, useState} from "react";
|
||||||
import {Link} from "react-router-dom";
|
import {Link, useLocation} from "react-router-dom";
|
||||||
|
import NavBar from "./NavBar.jsx";
|
||||||
|
|
||||||
function Projects() {
|
function Projects() {
|
||||||
const [projects, setProjects] = useState([]);
|
const [projects, setProjects] = useState([]);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchProjects = async () => {
|
const fetchProjects = async () => {
|
||||||
@ -22,24 +25,63 @@ function Projects() {
|
|||||||
fetchProjects();
|
fetchProjects();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <div>Erreur lors de la récupération des données : {error}</div>;
|
return <div>Error retrieving data: {error}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (location.pathname === '/') {
|
||||||
|
return (
|
||||||
|
<section id="projects-section">
|
||||||
|
<h1 className="section-title">Projects</h1>
|
||||||
|
<div className="projects-section-list">
|
||||||
|
{projects
|
||||||
|
.filter(project => project.id <= 3)
|
||||||
|
.map(project => (
|
||||||
|
<SingleProject
|
||||||
|
image={project.image_name}
|
||||||
|
title={project.title}
|
||||||
|
description={project.description}
|
||||||
|
skills={project.skills}
|
||||||
|
id={project.id}
|
||||||
|
nbImage={project.nb_image}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="show-more-container">
|
||||||
|
<Link to="/projects" className="projects-link">Show more</Link>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}else if (location.pathname === '/projects') {
|
||||||
|
return (
|
||||||
|
<section id="projects-section">
|
||||||
|
<div className="projects-section-header">
|
||||||
|
<NavBar />
|
||||||
|
<h1 className="section-title">All Projects</h1>
|
||||||
|
<p className="projects-section-subtitle">Here you can find a collection of my work.</p>
|
||||||
|
</div>
|
||||||
|
<div className="projects-section-list">
|
||||||
|
{projects.map(project => (
|
||||||
|
<SingleProject
|
||||||
|
image={project.image_name}
|
||||||
|
title={project.title}
|
||||||
|
description={project.description}
|
||||||
|
skills={project.skills}
|
||||||
|
id={project.id}
|
||||||
|
nbImage={project.nb_image}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="projects-back-link">
|
||||||
|
<Link to="/" className="projects-link">← Back to Home</Link>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}else {
|
||||||
|
return <div>Page inexistante</div>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
|
||||||
<section id="projects-section">
|
|
||||||
<h1 className="section-title">Projects</h1>
|
|
||||||
<div className="projects-section-list">
|
|
||||||
{projects.map(project => (
|
|
||||||
<SingleProject image={project.image_name} title={project.title} description={project.description} skills={project.skills} color={project.color} nbImage={project.nb_image}/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="show-more-container">
|
|
||||||
<Link to="/projets" className="show-more-link">Show more</Link>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -2,10 +2,11 @@ import { useState, useEffect, useRef } from "react";
|
|||||||
import SkillCard from "./SkillCard";
|
import SkillCard from "./SkillCard";
|
||||||
import "../styles/SingleProject.css";
|
import "../styles/SingleProject.css";
|
||||||
|
|
||||||
function SingleProject({ image, title, description, skills, color, nbImage }) {
|
function SingleProject({ image, title, description, skills, id, nbImage }) {
|
||||||
const [imageID, setImageID] = useState(1);
|
const [imageID, setImageID] = useState(1);
|
||||||
const [isFading, setIsFading] = useState(true);
|
const [isFading, setIsFading] = useState(true);
|
||||||
const intervalRef = useRef(null);
|
const intervalRef = useRef(null);
|
||||||
|
const color = ["blue", "green", "purple", "red", "yellow"]
|
||||||
|
|
||||||
const handleChangeImage = (direction) => {
|
const handleChangeImage = (direction) => {
|
||||||
if (nbImage <= 1) return;
|
if (nbImage <= 1) return;
|
||||||
@ -52,7 +53,7 @@ function SingleProject({ image, title, description, skills, color, nbImage }) {
|
|||||||
|
|
||||||
<div className="single-project-right">
|
<div className="single-project-right">
|
||||||
<div className="single-project-right-top">
|
<div className="single-project-right-top">
|
||||||
<div className={`single-project-line color-${color}`}></div>
|
<div className={`single-project-line color-${color[(id-1)%color.length]}`}></div>
|
||||||
<h3 className="single-project-title">{title}</h3>
|
<h3 className="single-project-title">{title}</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -23,7 +23,7 @@ function Skills() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <div>Erreur lors de la récupération des données : {error}</div>;
|
return <div>Error retrieving data: {error}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uniqueSkillTypes = [...new Set(skills.map(skill => skill.type))];
|
const uniqueSkillTypes = [...new Set(skills.map(skill => skill.type))];
|
||||||
|
|||||||
@ -1,9 +1,26 @@
|
|||||||
import Home from '../components/Home.jsx'
|
import {useEffect, useState} from 'react'; // 1. Import useEffect
|
||||||
import Experiences from '../components/Experiences.jsx'
|
import { useLocation } from 'react-router-dom'; // 2. Import useLocation
|
||||||
import Projects from '../components/Projects.jsx'
|
import Home from '../components/Home.jsx';
|
||||||
import Skills from '../components/Skills.jsx'
|
import Experiences from '../components/Experiences.jsx';
|
||||||
import Footer from '../components/Footer.jsx'
|
import Projects from '../components/Projects.jsx';
|
||||||
|
import Skills from '../components/Skills.jsx';
|
||||||
|
import Footer from '../components/Footer.jsx';
|
||||||
|
|
||||||
function HomePage() {
|
function HomePage() {
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (location.hash) {
|
||||||
|
const id = location.hash.replace('#', '');
|
||||||
|
const element = document.getElementById(id);
|
||||||
|
if (element) {
|
||||||
|
setTimeout(() => {
|
||||||
|
element.scrollIntoView({ behavior: 'smooth' });
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [location]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Home/>
|
<Home/>
|
||||||
|
|||||||
@ -1,38 +1,8 @@
|
|||||||
import React, {useEffect, useState} from "react";
|
import Projects from "../components/Projects.jsx";
|
||||||
import SingleProject from "../components/SingleProject.jsx";
|
|
||||||
function ProjectsPage() {
|
function ProjectsPage() {
|
||||||
const [projects, setProjects] = useState([]);
|
|
||||||
const [error, setError] = useState(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchProjects = async () => {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/api/projects/');
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Erreur HTTP: ${response.status}`);
|
|
||||||
}
|
|
||||||
const data = await response.json();
|
|
||||||
setProjects(data.data);
|
|
||||||
} catch (err) {
|
|
||||||
setError(err.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
fetchProjects();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return <div>Erreur lors de la récupération des données : {error}</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Projects/>
|
||||||
<h1>Voici mes projets</h1>;
|
);
|
||||||
{projects.map(project => (
|
|
||||||
<SingleProject image={project.image_name} title={project.title} description={project.description} skills={project.skills} color={project.color} nbImage={project.nb_image}/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
export default ProjectsPage;
|
export default ProjectsPage;
|
||||||
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.show-more-link {
|
.projects-link {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
color: #B0B0B0;
|
color: #B0B0B0;
|
||||||
border: 1px solid #B0B0B0;
|
border: 1px solid #B0B0B0;
|
||||||
@ -22,7 +22,7 @@
|
|||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.show-more-link:hover {
|
.projects-link:hover {
|
||||||
background-color: #B0B0B0;
|
background-color: #B0B0B0;
|
||||||
color: #1E1E1E;
|
color: #1E1E1E;
|
||||||
transform: translateY(-3px);
|
transform: translateY(-3px);
|
||||||
@ -31,4 +31,29 @@
|
|||||||
|
|
||||||
.projects-section-list {
|
.projects-section-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.projects-section-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.projects-section-subtitle {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: #B0B0B0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.projects-back-link {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 3rem;
|
||||||
}
|
}
|
||||||
@ -69,18 +69,27 @@
|
|||||||
margin-right: 1rem;
|
margin-right: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-orange {
|
.color-blue {
|
||||||
background-color: #D95F46;
|
background-color: #2556ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-green {
|
||||||
|
background-color: #36a837;
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-purple {
|
.color-purple {
|
||||||
background-color: #a646d9;
|
background-color: #a646d9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-green {
|
.color-red {
|
||||||
background-color: #36a837;
|
background-color: #ff0000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.color-yellow {
|
||||||
|
background-color: #ffd427;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.single-project-title {
|
.single-project-title {
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
color: #EAEAEA;
|
color: #EAEAEA;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user