feat: Add new sections and improve layout

- Added ExperienceSection and SkillsSection components to showcase user experience and skills.
- Updated App component to include new sections in the Home layout.
- Enhanced Projet component with improved styling and functionality, including a video player.
- Integrated new translations in English and French for additional sections.
- Updated navigation bar to include links to new sections.
- Added Dockerfile and nginx configuration for deployment.
- Removed unused video files and optimized existing components for better performance.
- Updated CVSection to correctly reference the CV file path.
- Improved styling across various components for a more cohesive design.
This commit is contained in:
2026-02-24 12:23:27 +01:00
parent b012f28dd8
commit 83560e8086
21 changed files with 924 additions and 194 deletions
+17
View File
@@ -0,0 +1,17 @@
FROM node:18-alpine AS build
WORKDIR /app
ENV NODE_ENV=production
# Install dependencies
COPY package*.json ./
RUN npm ci --silent
# Copy source and build
COPY . .
RUN npm run build
FROM nginx:stable-alpine AS production
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
+15
View File
@@ -0,0 +1,15 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
include /etc/nginx/mime.types;
types {
application/javascript mjs;
}
location / {
try_files $uri $uri/ /index.html;
}
}
+94 -49
View File
@@ -12,6 +12,7 @@
"@emotion/styled": "^11.14.1",
"@hookform/resolvers": "^5.2.2",
"@mui/icons-material": "^7.3.2",
"@mui/lab": "^7.0.1-beta.22",
"@mui/material": "^7.3.2",
"@mui/x-data-grid": "^8.25.0",
"@mui/x-data-grid-generator": "^8.25.0",
@@ -275,9 +276,9 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
"integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz",
"integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -1254,9 +1255,9 @@
}
},
"node_modules/@mui/core-downloads-tracker": {
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.2.tgz",
"integrity": "sha512-AOyfHjyDKVPGJJFtxOlept3EYEdLoar/RvssBTWVAvDJGIE676dLi2oT/Kx+FoVXFoA/JdV7DEMq/BVWV3KHRw==",
"version": "7.3.8",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.8.tgz",
"integrity": "sha512-s9UHZo7QJVly7gNArEZkbbsimHqJZhElgBpXIJdehZ4OWXt+CCr0SBDgUCDJnQrqpd1dWK2dLq5rmO4mCBmI3w==",
"license": "MIT",
"funding": {
"type": "opencollective",
@@ -1289,23 +1290,67 @@
}
}
},
"node_modules/@mui/material": {
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.2.tgz",
"integrity": "sha512-qXvbnawQhqUVfH1LMgMaiytP+ZpGoYhnGl7yYq2x57GYzcFL/iPzSZ3L30tlbwEjSVKNYcbiKO8tANR1tadjUg==",
"node_modules/@mui/lab": {
"version": "7.0.1-beta.22",
"resolved": "https://registry.npmjs.org/@mui/lab/-/lab-7.0.1-beta.22.tgz",
"integrity": "sha512-NHNIg51/CxiJTMTaWCxmqlqYIUfpXbdVjv/OyhSmuAb0z/lIOCMMu9fz0bIbQnZEh6H5L7DhTblynT7xwkXMIQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.3",
"@mui/core-downloads-tracker": "^7.3.2",
"@mui/system": "^7.3.2",
"@mui/types": "^7.4.6",
"@mui/utils": "^7.3.2",
"@babel/runtime": "^7.28.6",
"@mui/system": "^7.3.8",
"@mui/types": "^7.4.11",
"@mui/utils": "^7.3.8",
"clsx": "^2.1.1",
"prop-types": "^15.8.1"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0",
"@mui/material": "^7.3.8",
"@mui/material-pigment-css": "^7.3.8",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/react": {
"optional": true
},
"@emotion/styled": {
"optional": true
},
"@mui/material-pigment-css": {
"optional": true
},
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/material": {
"version": "7.3.8",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.8.tgz",
"integrity": "sha512-QKd1RhDXE1hf2sQDNayA9ic9jGkEgvZOf0tTkJxlBPG8ns8aS4rS8WwYURw2x5y3739p0HauUXX9WbH7UufFLw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.6",
"@mui/core-downloads-tracker": "^7.3.8",
"@mui/system": "^7.3.8",
"@mui/types": "^7.4.11",
"@mui/utils": "^7.3.8",
"@popperjs/core": "^2.11.8",
"@types/react-transition-group": "^4.4.12",
"clsx": "^2.1.1",
"csstype": "^3.1.3",
"csstype": "^3.2.3",
"prop-types": "^15.8.1",
"react-is": "^19.1.1",
"react-is": "^19.2.3",
"react-transition-group": "^4.4.5"
},
"engines": {
@@ -1318,7 +1363,7 @@
"peerDependencies": {
"@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0",
"@mui/material-pigment-css": "^7.3.2",
"@mui/material-pigment-css": "^7.3.8",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
@@ -1339,13 +1384,13 @@
}
},
"node_modules/@mui/private-theming": {
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.2.tgz",
"integrity": "sha512-ha7mFoOyZGJr75xeiO9lugS3joRROjc8tG1u4P50dH0KR7bwhHznVMcYg7MouochUy0OxooJm/OOSpJ7gKcMvg==",
"version": "7.3.8",
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.8.tgz",
"integrity": "sha512-du5dlPZ9XL3xW2apHoGDXBI+QLtyVJGrXNCfcNYfP/ojkz1RQ0rRV6VG9Rkm1DqEFRG8mjjTL7zmE1Bvn1eR4A==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.3",
"@mui/utils": "^7.3.2",
"@babel/runtime": "^7.28.6",
"@mui/utils": "^7.3.8",
"prop-types": "^15.8.1"
},
"engines": {
@@ -1366,16 +1411,16 @@
}
},
"node_modules/@mui/styled-engine": {
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.2.tgz",
"integrity": "sha512-PkJzW+mTaek4e0nPYZ6qLnW5RGa0KN+eRTf5FA2nc7cFZTeM+qebmGibaTLrgQBy3UpcpemaqfzToBNkzuxqew==",
"version": "7.3.8",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.8.tgz",
"integrity": "sha512-JHAeXQzS0tJ+Fq3C6J4TVDsW+yKhO4uuxuiLaopNStJeQYBIUCXpKYyUCcgXym4AmhbznQnv9RlHywSH6b0FOg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.3",
"@babel/runtime": "^7.28.6",
"@emotion/cache": "^11.14.0",
"@emotion/serialize": "^1.3.3",
"@emotion/sheet": "^1.4.0",
"csstype": "^3.1.3",
"csstype": "^3.2.3",
"prop-types": "^15.8.1"
},
"engines": {
@@ -1400,18 +1445,18 @@
}
},
"node_modules/@mui/system": {
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.2.tgz",
"integrity": "sha512-9d8JEvZW+H6cVkaZ+FK56R53vkJe3HsTpcjMUtH8v1xK6Y1TjzHdZ7Jck02mGXJsE6MQGWVs3ogRHTQmS9Q/rA==",
"version": "7.3.8",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.8.tgz",
"integrity": "sha512-hoFRj4Zw2Km8DPWZp/nKG+ao5Jw5LSk2m/e4EGc6M3RRwXKEkMSG4TgtfVJg7dS2homRwtdXSMW+iRO0ZJ4+IA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.3",
"@mui/private-theming": "^7.3.2",
"@mui/styled-engine": "^7.3.2",
"@mui/types": "^7.4.6",
"@mui/utils": "^7.3.2",
"@babel/runtime": "^7.28.6",
"@mui/private-theming": "^7.3.8",
"@mui/styled-engine": "^7.3.8",
"@mui/types": "^7.4.11",
"@mui/utils": "^7.3.8",
"clsx": "^2.1.1",
"csstype": "^3.1.3",
"csstype": "^3.2.3",
"prop-types": "^15.8.1"
},
"engines": {
@@ -1440,12 +1485,12 @@
}
},
"node_modules/@mui/types": {
"version": "7.4.10",
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.10.tgz",
"integrity": "sha512-0+4mSjknSu218GW3isRqoxKRTOrTLd/vHi/7UC4+wZcUrOAqD9kRk7UQRL1mcrzqRoe7s3UT6rsRpbLkW5mHpQ==",
"version": "7.4.11",
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.11.tgz",
"integrity": "sha512-fZ2xO9D08IKOxO2oUBi1nnVKH6oJUD+64cnv4YAaFoC0E5+i1+S5AHbNqqvZlYYsbPEQ6qEVwuBqY3jl5W4G+Q==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.4"
"@babel/runtime": "^7.28.6"
},
"peerDependencies": {
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
@@ -1457,13 +1502,13 @@
}
},
"node_modules/@mui/utils": {
"version": "7.3.7",
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.7.tgz",
"integrity": "sha512-+YjnjMRnyeTkWnspzoxRdiSOgkrcpTikhNPoxOZW0APXx+urHtUoXJ9lbtCZRCA5a4dg5gSbd19alL1DvRs5fg==",
"version": "7.3.8",
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.8.tgz",
"integrity": "sha512-kZRcE2620CBGr+XI8YMmwPj6WIPwSF7uMJjvSfqd8zXVvlz0MCJbzRRUGNf8NgflCLthdji2DdS643TeyJ3+nA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.4",
"@mui/types": "^7.4.10",
"@babel/runtime": "^7.28.6",
"@mui/types": "^7.4.11",
"@types/prop-types": "^15.7.15",
"clsx": "^2.1.1",
"prop-types": "^15.8.1",
@@ -3662,9 +3707,9 @@
}
},
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"license": "MIT"
},
"node_modules/dayjs": {
+1
View File
@@ -15,6 +15,7 @@
"@emotion/styled": "^11.14.1",
"@hookform/resolvers": "^5.2.2",
"@mui/icons-material": "^7.3.2",
"@mui/lab": "^7.0.1-beta.22",
"@mui/material": "^7.3.2",
"@mui/x-data-grid": "^8.25.0",
"@mui/x-data-grid-generator": "^8.25.0",
BIN
View File
Binary file not shown.
+4 -1
View File
@@ -10,5 +10,8 @@
"SEE_ALL_PROJECTS": "See All Projects",
"CONTACT_ME": "Contact Me",
"CREATE_PROJECT": "Create Project",
"EDIT_PROJECT": "Edit Project"
"EDIT_PROJECT": "Edit Project",
"MY_EXPERIENCE": "My Experience",
"SKILLS": "Skills"
}
+4 -1
View File
@@ -10,5 +10,8 @@
"SEE_ALL_PROJECTS": "Voir tous les projets",
"CONTACT_ME": "Contactez-moi",
"CREATE_PROJECT": "Créer un projet",
"EDIT_PROJECT": "Modifier le projet"
"EDIT_PROJECT": "Modifier le projet",
"MY_EXPERIENCE": "Mon expérience",
"SKILLS": "Compétences"
}
Binary file not shown.
Binary file not shown.
+10 -4
View File
@@ -1,20 +1,26 @@
import { Outlet } from "react-router-dom";
import { Container, Typography, Box, IconButton, Stack } from "@mui/material";
import { Container, Box } from "@mui/material";
import AppBar from "./section/navBar";
import "./App.css";
import Footer from "./section/footer";
export default function App() {
return (
<Box sx={{ minHeight: "100vh", display: "flex", flexDirection: "column" }}>
<Box
sx={{
minHeight: "100vh",
display: "flex",
flexDirection: "column",
bgcolor: "#fafbfc",
}}
>
<AppBar />
<Container
sx={{ flex: 1, display: "flex", flexDirection: "column", minHeight: 0 }}
maxWidth="xl"
>
<Outlet />
</Container>
<Footer />
</Box>
);
}
+3 -1
View File
@@ -79,7 +79,9 @@ export default function Card_Projet({
{name}
</Typography>
<Typography>{shortDescription}</Typography>
<Typography variant="body1" component="p">
{shortDescription}
</Typography>
</Box>
<Box
+4 -4
View File
@@ -30,8 +30,8 @@ export default function MenuLangue() {
}}
>
<img
src={`drapeau/${i18n.language}.webp`}
alt={`drapeau ${i18n.language}`}
src={`/drapeau/${i18n.language}.webp`}
alt={`drapeau/${i18n.language}.webp`}
style={{ width: "30px", height: "20px", marginRight: "8px" }}
/>
</Button>
@@ -57,7 +57,7 @@ export default function MenuLangue() {
}}
>
<img
src="drapeau/fr.webp"
src="/drapeau/fr.webp"
alt={t("FRENCH")}
style={{ width: "30px", height: "20px", marginRight: "8px" }}
/>
@@ -70,7 +70,7 @@ export default function MenuLangue() {
}}
>
<img
src="drapeau/en.webp"
src="/drapeau/en.webp"
alt={t("ENGLISH")}
style={{ width: "30px", height: "20px", marginRight: "8px" }}
/>
+4
View File
@@ -2,13 +2,17 @@ import AboutSection from "./home/AboutSection";
import CVSection from "./home/CVSection";
import HeroSection from "./home/HeroSection";
import ProjetSection from "./home/ProjetSection";
import ExperienceSection from "./home/ExperienceSection";
import SkillsSection from "./home/SkillsSection";
export default function Home() {
return (
<>
<HeroSection />
<AboutSection />
<SkillsSection />
<ProjetSection />
<ExperienceSection />
<CVSection />
</>
);
+310 -11
View File
@@ -1,21 +1,320 @@
import { useParams } from "react-router-dom";
import React from "react";
import { useParams, useNavigate } from "react-router-dom";
import { useProjet } from "../hook/useProjet";
import {
Box,
Container,
Typography,
Button,
Chip,
CircularProgress,
Stack,
Paper,
Grid,
Divider,
} from "@mui/material";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import GitHubIcon from "@mui/icons-material/GitHub";
import LanguageIcon from "@mui/icons-material/Language";
export default function Projet() {
const { id } = useParams();
const navigate = useNavigate(); // Permet de gérer le bouton "Retour"
const apiprojet = {
projets: [
{
id: 1,
name: "protfolio",
shortdescriptionfr:
"Un portfolio personnel développé avec React et Material-UI, mettant en avant mes compétences, expériences et projets de manière moderne et responsive. avec une API en Go pour la gestion des données.",
technologies: ["React", "Node.js", "Go", "SQL"],
longdescriptionfr:
"Ce projet de portfolio personnel a été conçu pour présenter mes compétences, expériences et réalisations de manière professionnelle et attrayante. Développé avec React pour une interface utilisateur dynamique et Material-UI pour un design moderne, il offre une navigation fluide et responsive sur tous les appareils. L'API en Go gère efficacement les données des projets, assurant une performance optimale. Ce portfolio met en avant mes projets phares, mes compétences techniques et mon parcours professionnel, offrant aux visiteurs une vue d'ensemble complète de mon expertise en développement web.",
shortdescriptionen:
"A personal portfolio developed with React and Material-UI, showcasing my skills, experiences, and projects in a modern and responsive way. It includes a Go API for data management.",
longdescriptionen:
"This personal portfolio project was designed to showcase my skills, experiences, and achievements in a professional and attractive manner. Developed with React for a dynamic user interface and Material-UI for a modern design, it offers smooth and responsive navigation across all devices. The Go API efficiently manages project data, ensuring optimal performance. This portfolio highlights my key projects, technical skills, and professional background, providing visitors with a comprehensive overview of my expertise in web development.",
linkGithub: "https://github.com/sylvaintr/portfolio-react",
linkWeb: "http://localhost:5173/",
},
{
id: 2,
name: "Projet B",
shortdescriptionfr: "Description courte du projet B",
technologies: ["Go", "MongoDB"],
},
{
id: 3,
name: "VHS Video Home Share",
shortdescriptionfr:
"VHS est une plateforme web interactive qui transforme le streaming solitaire en une expérience collective. En mélangeant nostalgie et technologie moderne, l'application recrée la convivialité des cinémas et des soirées télé d'autrefois.",
technologies: ["php", "javascript", "twig", "SQL"],
},
],
};
const { data, isLoading } = useProjet(id);
const isLoading = false;
const data = apiprojet.projets.find((p) => p.id === Number(id));
const primaryBlue = "#7ab2cb";
// 1. ÉTAT DE CHARGEMENT PROPRE
if (isLoading) {
return (
<>
<h1>Projet {id}</h1>
{isLoading && <p>Loading...</p>}
{!isLoading && data && (
<div>
<h2>{data.name}</h2>
<p>{data.shortdescriptionfr}</p>
</div>
<Box
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
minHeight: "60vh",
}}
>
<CircularProgress sx={{ color: primaryBlue }} size={60} thickness={4} />
</Box>
);
}
// 2. GESTION DE L'ERREUR (Si le projet n'existe pas)
if (!data) {
return (
<Container sx={{ py: 10, textAlign: "center" }}>
<Typography variant="h4" color="text.secondary" gutterBottom>
Projet introuvable
</Typography>
<Button
startIcon={<ArrowBackIcon />}
onClick={() => navigate(-1)}
sx={{ color: primaryBlue }}
>
Retour aux projets
</Button>
</Container>
);
}
return (
<Box sx={{ py: 6, bgcolor: "#fafbfc", minHeight: "100vh" }}>
<Container maxWidth="lg">
{/* BOUTON RETOUR */}
<Button
startIcon={<ArrowBackIcon />}
onClick={() => navigate(-1)}
sx={{ mb: 4, color: "#666", textTransform: "none" }}
>
Retour
</Button>
{/* GRAND CONTENEUR BLANC PRINCIPAL */}
<Paper
elevation={0}
sx={{
p: { xs: 4, md: 6 },
borderRadius: 4,
border: "1px solid #eef2f6",
bgcolor: "white",
boxShadow: "0 12px 30px rgba(0,0,0,0.03)",
// overflow: "hidden" retiré ici pour éviter de couper les ombres
}}
>
<Grid container spacing={4} alignItems="flex-start">
{/* COLONNE GAUCHE (Texte) : Prend 7 colonnes sur 12 */}
<Grid item xs={12} md={7}>
<Typography
variant="h2"
sx={{
fontWeight: 800,
color: "#2c3e50",
mb: 2,
fontSize: { xs: "2rem", md: "3rem" },
}}
>
{data.name}
</Typography>
<Typography
variant="h6"
sx={{ color: primaryBlue, fontWeight: 400, lineHeight: 1.6 }}
>
{data.shortdescriptionfr}
</Typography>
</Grid>
{/* COLONNE DROITE (Vidéo) : Prend 5 colonnes sur 12 */}
<Grid item xs={12} md={5}>
<Box
sx={{
width: "100%",
borderRadius: 3,
overflow: "hidden",
boxShadow: "0 8px 25px rgba(0,0,0,0.08)",
bgcolor: "#444", // Fond sombre élégant
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<video
autoPlay
loop
muted
controls
style={{
width: "100%",
maxHeight: "300px", // Hauteur max de la vidéo
display: "block",
objectFit: "contain",
}}
>
<source
src={`/video_projet/projet_${id}.mp4`}
type="video/mp4"
/>
Votre navigateur ne supporte pas les vidéos.
</video>
</Box>
</Grid>
</Grid>
<Divider sx={{ my: 6, borderColor: "#f0f0f0" }} />
{/* 2. SECTION MILIEU : DESCRIPTION LONGUE (Prend toute la largeur) */}
<Box sx={{ mb: 6 }}>
<Typography
variant="h5"
sx={{ fontWeight: 700, mb: 3, color: "#333" }}
>
À propos de ce projet
</Typography>
<Typography
variant="body1"
sx={{
color: "#555",
lineHeight: 1.8,
fontSize: "1.05rem",
whiteSpace: "pre-line",
}}
>
{data.longdescriptionfr}
</Typography>
</Box>
{/* 3. SECTION BAS : TECHNOS & LIENS (Alignés, sans déborder) */}
{/* J'ai utilisé alignItems="stretch" pour que les deux cartes aient la même hauteur naturellement */}
<Grid container spacing={4} alignItems="stretch">
{/* TECHNOLOGIES */}
<Grid item xs={12} md={6}>
<Paper
elevation={0}
sx={{
p: 4,
bgcolor: "#f8fafd",
borderRadius: 3,
border: "1px solid #e8f0f4",
height: "100%", // Permet à la carte de prendre toute la hauteur disponible de la grille
boxSizing: "border-box",
}}
>
<Typography
variant="h6"
sx={{ fontWeight: 700, mb: 3, color: "#333" }}
>
Technologies utilisées
</Typography>
{data.technologies && data.technologies.length > 0 ? (
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 1.5 }}>
{data.technologies.map((tech, idx) => (
<Chip
key={idx}
label={tech}
sx={{
bgcolor: "white",
color: primaryBlue,
fontWeight: 600,
border: "1px solid",
borderColor: "rgba(122, 178, 203, 0.3)",
py: 1,
}}
/>
))}
</Box>
) : (
<Typography variant="body2" color="text.secondary">
Non spécifié
</Typography>
)}
</>
</Paper>
</Grid>
{/* LIENS */}
<Grid item xs={12} md={6}>
<Paper
elevation={0}
sx={{
p: 4,
bgcolor: "#f8fafd",
borderRadius: 3,
border: "1px solid #e8f0f4",
height: "100%",
boxSizing: "border-box",
}}
>
<Typography
variant="h6"
sx={{ fontWeight: 700, mb: 3, color: "#333" }}
>
Liens du projet
</Typography>
<Stack
spacing={2}
direction={{ xs: "column", sm: "row" }}
flexWrap="wrap"
useFlexGap
>
{data.linkWeb && (
<Button
variant="contained"
startIcon={<LanguageIcon />}
href={data.linkWeb}
target="_blank"
disableElevation
sx={{
bgcolor: primaryBlue,
textTransform: "none",
borderRadius: 2,
py: 1,
px: 3,
"&:hover": { bgcolor: "#689cb5" },
}}
>
Voir le site en direct
</Button>
)}
{data.linkGithub && (
<Button
variant="outlined"
startIcon={<GitHubIcon />}
href={data.linkGithub}
target="_blank"
sx={{
color: "#444",
borderColor: "#ccc",
textTransform: "none",
borderRadius: 2,
py: 1,
px: 3,
"&:hover": { borderColor: "#333", bgcolor: "white" },
}}
>
Code source (GitHub)
</Button>
)}
</Stack>
</Paper>
</Grid>
</Grid>
</Paper>
</Container>
</Box>
);
}
+4 -100
View File
@@ -1,36 +1,9 @@
import {
Avatar,
Box,
Container,
Grid,
Paper,
Stack,
Typography,
} from "@mui/material";
import { Avatar, Box, Stack, Typography } from "@mui/material";
import { useTranslation } from "react-i18next";
import DevicesIcon from "@mui/icons-material/Devices";
import StorageIcon from "@mui/icons-material/Storage";
import CodeIcon from "@mui/icons-material/Code";
export default function AboutSection() {
const { t } = useTranslation();
const skills = [
{
name: "Frontend",
icon: <DevicesIcon sx={{ fontSize: 40, color: "#7ab2cb" }} />,
desc: "React, MUI, Tailwind",
},
{
name: "Backend",
icon: <StorageIcon sx={{ fontSize: 40, color: "#ff4081" }} />,
desc: "Node.js, Go, SQL",
},
{
name: "Outils",
icon: <CodeIcon sx={{ fontSize: 40, color: "#9c27b0" }} />,
desc: "Git, Docker, Figma",
},
];
return (
<>
<Typography
@@ -45,76 +18,7 @@ export default function AboutSection() {
>
{t("ABOUT_ME")}
</Typography>
<Container maxWidth="lg" sx={{ my: 10 }}>
<Grid container spacing={6} alignItems="center">
{/* PARTIE GAUCHE : LE TEXTE */}
<Grid item xs={12} md={6}>
{/* Titre propre sans la grosse barre grise */}
<Typography
variant="h3"
fontWeight="800"
sx={{ mb: 3, color: "#2c3e50" }}
>
À propos de{" "}
<Box component="span" sx={{ color: "#7ab2cb" }}>
moi
</Box>
</Typography>
<Typography
variant="body1"
sx={{ fontSize: "1.1rem", color: "#555", lineHeight: 1.8, mb: 2 }}
>
Étudiant en troisième année de BUT Informatique à Anglet.
Passionné par le développement, j'aime explorer les nouvelles
technologies et résoudre des problèmes complexes.
</Typography>
<Typography
variant="body1"
sx={{ fontSize: "1.1rem", color: "#555", lineHeight: 1.8 }}
>
Mon objectif est de concevoir des applications performantes qui
ont un réel impact utilisateur. Quand je ne code pas, je fais de
la veille technologique sur l'écosystème Go et React.
</Typography>
</Grid>
{/* PARTIE DROITE : REMPLACEMENT DE LA PHOTO PAR DES "CARTES COMPETENCES" */}
<Grid item xs={12} md={6}>
<Grid container spacing={2}>
{skills.map((skill, index) => (
<Grid item xs={12} sm={6} key={index}>
<Paper
elevation={0}
sx={{
p: 3,
height: "100%",
bgcolor: "#f9f9f9",
borderRadius: 4,
border: "1px solid #eee",
transition: "all 0.3s ease",
"&:hover": {
transform: "translateY(-5px)",
boxShadow: "0 10px 20px rgba(0,0,0,0.05)",
borderColor: "#7ab2cb",
},
}}
>
<Box sx={{ mb: 2 }}>{skill.icon}</Box>
<Typography variant="h6" fontWeight="bold" gutterBottom>
{skill.name}
</Typography>
<Typography variant="body2" color="text.secondary">
{skill.desc}
</Typography>
</Paper>
</Grid>
))}
</Grid>
</Grid>
</Grid>
</Container>
<Stack
direction={{ xs: "column", sm: "row" }}
sx={{ alignItems: "center" }}
@@ -122,11 +26,11 @@ export default function AboutSection() {
mb={4}
>
<Box>
<Typography sx={{ textAlign: "justify" }}>
<Typography sx={{ textAlign: "justify", fontSize: "1.1rem" }} variant="body1" component="p">
{t("Paragraph1_ABOUT_ME")}
</Typography>
<br />
<Typography sx={{ textAlign: "justify" }}>
<Typography sx={{ textAlign: "justify", fontSize: "1.1rem" }} variant="body1" component="p">
{t("Paragraph2_ABOUT_ME")}
</Typography>
</Box>
+3 -1
View File
@@ -28,10 +28,12 @@ export default function CVSection() {
alignItems: "center",
mx: "auto",
width: "100%",
maxWidth: "100%",
overflow: "hidden",
mb: 4,
}}
>
<Document file="cv.pdf">
<Document file="/cv.pdf">
<Page
pageNumber={1}
renderTextLayer={false}
+177
View File
@@ -0,0 +1,177 @@
import {
Box,
Typography,
Paper,
Chip,
useMediaQuery,
useTheme,
} from "@mui/material";
import {
Timeline,
TimelineItem,
TimelineSeparator,
TimelineConnector,
TimelineContent,
TimelineDot,
TimelineOppositeContent,
} from "@mui/lab";
import LaptopMacIcon from "@mui/icons-material/LaptopMac";
import SchoolIcon from "@mui/icons-material/School";
import WorkIcon from "@mui/icons-material/Work";
import { useTranslation } from "react-i18next";
// Tes données
const experiences = [
{
title: "Développeur Full Stack",
company: "Freelance",
date: "2022 - Présent",
icon: <LaptopMacIcon />,
technos: ["React", "Node.js", "Docker"],
desc: "Développement d'applications web complètes et déploiement CI/CD.",
},
{
title: "Développeur Front-end",
company: "Agence Web Exemple",
date: "2020 - 2022",
icon: <WorkIcon />,
technos: ["TypeScript", "React", "Sass"],
desc: "Création d'interfaces responsives et migration Angular vers React.",
},
{
title: "BUT Informatique",
company: "IUT de Bayonne",
date: "2018 - 2021",
icon: <SchoolIcon />,
technos: ["Java", "SQL", "Algo"],
desc: "Apprentissage des bases de l'ingénierie logicielle et gestion de projet.",
},
];
export default function ExperienceTimeline() {
const theme = useTheme();
const { t } = useTranslation();
// Détecte si on est sur mobile pour changer l'affichage
const isMobile = useMediaQuery(theme.breakpoints.down("md"));
const primaryBlue = "#7ab2cb";
return (
<Box sx={{ bgcolor: "#fff" }} id="experience">
{/* TITRE */}
<Typography
variant="h2"
id="MY_EXPERIENCE"
sx={{
bgcolor: "#d8d7d7",
py: 1,
mb: 4,
borderLeft: "10px solid #d8d7d7",
}}
>
{t("MY_EXPERIENCE")}
</Typography>
{/* TIMELINE */}
{/* 'position="alternate"' crée l'effet zig-zag automatiquement */}
<Timeline
position={isMobile ? "right" : "alternate"}
sx={{ bgcolor: "#fafbfc" }}
>
{experiences.map((exp, index) => (
<TimelineItem key={index}>
{/* LA DATE (s'affiche en face du contenu sur PC) */}
<TimelineOppositeContent
sx={{
m: "auto 0",
display: isMobile ? "none" : "block", // On cache la date opposée sur mobile pour la mettre dans la carte
color: "#666",
fontWeight: "bold",
fontSize: "1.1rem",
}}
>
{exp.date}
</TimelineOppositeContent>
{/* LE SÉPARATEUR CENTRAL */}
<TimelineSeparator>
<TimelineConnector sx={{ bgcolor: primaryBlue }} />
<TimelineDot
sx={{
bgcolor: primaryBlue,
p: 2,
boxShadow: "0 0 0 4px rgba(122, 178, 203, 0.2)", // Petit effet de halo autour du point
}}
>
{exp.icon}
</TimelineDot>
<TimelineConnector sx={{ bgcolor: primaryBlue }} />
</TimelineSeparator>
{/* LE CONTENU (LA CARTE) */}
<TimelineContent sx={{ py: "12px", px: 2 }}>
<Paper
elevation={3}
sx={{
p: 3,
bgcolor: "white",
borderRadius: 4,
textAlign: "left",
position: "relative",
"&:hover": {
transform: "translateY(-3px)",
boxShadow: "0 10px 20px rgba(0,0,0,0.1)",
},
transition: "all 0.3s ease",
borderTop: `4px solid ${primaryBlue}`, // Petite touche de couleur en haut de la carte
}}
>
{/* Sur mobile, on affiche la date DANS la carte car il n'y a pas de place à côté */}
{isMobile && (
<Typography
variant="caption"
sx={{
color: "#888",
display: "block",
mb: 1,
fontWeight: "bold",
}}
>
{exp.date}
</Typography>
)}
<Typography variant="h6" component="span" fontWeight="bold">
{exp.title}
</Typography>
<Typography
variant="subtitle1"
sx={{ color: primaryBlue, mb: 1 }}
>
{exp.company}
</Typography>
<Typography variant="body2" color="text.secondary" paragraph>
{exp.desc}
</Typography>
{/* Chips technologies */}
<Box
sx={{ display: "flex", flexWrap: "wrap", gap: 0.5, mt: 1 }}
>
{exp.technos.map((tech, i) => (
<Chip
key={i}
label={tech}
size="small"
sx={{ bgcolor: "#f0f4f8", color: "#555" }}
/>
))}
</Box>
</Paper>
</TimelineContent>
</TimelineItem>
))}
</Timeline>
</Box>
);
}
+135 -16
View File
@@ -1,21 +1,53 @@
import { Box, Button, Container, Stack, Typography } from "@mui/material";
import { useTranslation } from "react-i18next";
import {
Box,
Button,
Container,
Stack,
Typography,
IconButton,
Tooltip,
} from "@mui/material";
import DownloadIcon from "@mui/icons-material/Download";
import LinkedInIcon from "@mui/icons-material/LinkedIn";
import GitHubIcon from "@mui/icons-material/GitHub";
import EmailIcon from "@mui/icons-material/Email";
export default function HeroSection() {
const { t } = useTranslation();
const primaryBlue = "#7ab2cb"; // Ta couleur principale
// Définition des liens (remplace les href par tes vrais liens)
const socialLinks = [
{
icon: <LinkedInIcon />,
label: "LinkedIn",
url: import.meta.env.VITE_LINK_TO_LINKEDIN,
color: "#0077b5", // Couleur officielle LinkedIn
},
{
icon: <GitHubIcon />,
label: "GitHub",
url: import.meta.env.VITE_LINK_TO_GITHUB,
color: "#333", // Couleur officielle GitHub
},
{
icon: <EmailIcon />,
label: "Email",
url: `mailto:${import.meta.env.VITE_MAIL}`,
color: "#ea4335", // Couleur Gmail/Rouge
text: import.meta.env.VITE_MAIL,
},
];
return (
<Box
sx={{
background: "linear-gradient(135deg, #f5f7fa 0%, #eef2f3 100%)", // Dégradé plus subtil
background: "linear-gradient(135deg, #f5f7fa 0%, #eef2f3 100%)",
py: { xs: 8, md: 12 },
mb: 8,
minHeight: "80vh", // Prend 100% de la hauteur de l'écran visible
display: "flex", // Active flexbox
alignItems: "center", // Centre le contenu verticalement
pt: { xs: 8, md: 0 }, // Un peu de padding en haut sur mobile, 0 sur PC car centré
minHeight: "80vh",
display: "flex",
alignItems: "center",
pt: { xs: 8, md: 0 },
px: 2,
borderRadius: "0 0 60px 60px",
position: "relative",
@@ -46,7 +78,7 @@ export default function HeroSection() {
<Typography
variant="h1"
sx={{
fontSize: { xs: "3rem", md: "5rem" }, // Plus grand pour l'impact
fontSize: { xs: "3rem", md: "5rem" },
fontWeight: 800,
color: "#2c3e50",
lineHeight: 1.2,
@@ -66,50 +98,137 @@ export default function HeroSection() {
mb: 4,
fontWeight: 400,
maxWidth: "600px",
mx: { xs: "auto", md: 0 }, // Centre le texte sur mobile
}}
>
Développeur Full Stack passionné par la création d'expériences web
modernes et intuitives.
</Typography>
{/* Boutons d'action */}
{/* Zone des Boutons (CV + Réseaux Sociaux) */}
<Stack
direction={{ xs: "column", sm: "row" }}
spacing={2}
direction={{ xs: "column", sm: "row" }} // Colonne sur mobile, ligne sur PC
spacing={3} // Espace entre le bouton CV et les icônes
alignItems="center"
justifyContent={{ xs: "center", md: "flex-start" }}
>
{/* Bouton CV */}
<Button
variant="outlined"
size="large"
href="/cv.pdf" // Mets le lien vers ton CV ici
target="_blank"
startIcon={<DownloadIcon />}
sx={{
color: "#555",
borderColor: "#ccc",
borderRadius: "20px",
px: 4,
px: 2,
py: 1.5,
fontSize: "1rem",
textTransform: "none",
backgroundColor: "white",
boxShadow: "0 4px 6px rgba(0,0,0,0.05)",
"&:hover": {
borderColor: primaryBlue,
color: primaryBlue,
bgcolor: "transparent",
bgcolor: "rgba(122, 178, 203, 0.1)",
transform: "translateY(-2px)", // Petit effet de levée
boxShadow: "0 6px 12px rgba(0,0,0,0.1)",
},
transition: "all 0.3s ease",
}}
>
Télécharger CV
</Button>
{/* Barre séparatrice verticale (visible uniquement sur écran large) */}
<Box
sx={{
display: { xs: "none", sm: "block" },
height: "30px",
width: "1px",
bgcolor: "#ddd",
}}
/>
{/* Icônes Réseaux Sociaux */}
<Stack direction="row" spacing={1}>
{socialLinks.map((social, index) => {
const hasText = !!social.text;
const baseSx = {
color: "#555",
bgcolor: "white",
border: "1px solid #eee",
transition: "all 0.3s ease",
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
gap: 1,
"&:hover": {
color: social.color,
bgcolor: "white",
transform: "translateY(-3px)",
borderColor: social.color,
boxShadow: "0 4px 12px rgba(0,0,0,0.1)",
},
};
const textSx = {
border: `1px solid ${primaryBlue}`,
borderColor: "#ccc",
borderRadius: "20px",
p: 1.2,
fontSize: "1rem",
textTransform: "none",
boxShadow: "0 4px 6px rgba(0,0,0,0.05)",
"&:hover": {
borderColor: primaryBlue,
color: primaryBlue,
bgcolor: "rgba(122, 178, 203, 0.1)",
transform: "translateY(-2px)",
boxShadow: "0 6px 12px rgba(0,0,0,0.1)",
},
};
return (
<Tooltip title={social.label} key={index} arrow>
<IconButton
component="a"
href={social.url}
target="_blank"
rel="noopener noreferrer"
sx={hasText ? { ...baseSx, ...textSx } : baseSx}
>
{social.icon}
{hasText && (
<Typography
variant="body2"
sx={{
ml: 1,
color: "#555",
fontSize: "0.95rem",
display: { xs: "none", sm: "inline" },
}}
>
{social.text}
</Typography>
)}
</IconButton>
</Tooltip>
);
})}
</Stack>
</Stack>
</Box>
{/* Image / Avatar à droite */}
<Box sx={{ flex: 1, display: "flex", justifyContent: "center" }}>
{/* Remplace le src par ta photo */}
<Box
sx={{
width: { xs: 250, md: 350 },
height: { xs: 250, md: 350 },
borderRadius: "30% 70% 70% 30% / 30% 30% 70% 70%", // Forme organique moderne
borderRadius: "30% 70% 70% 30% / 30% 30% 70% 70%",
bgcolor: "white",
boxShadow: "0 20px 40px rgba(0,0,0,0.1)",
overflow: "hidden",
+30 -4
View File
@@ -1,14 +1,40 @@
import { Box, Button, Stack, Typography } from "@mui/material";
import { useTranslation } from "react-i18next";
import Card_Projet from "../../components/Card_Projet";
import { useProjets } from "../../hook/useProjet";
//import { useProjets } from "../../hook/useProjet";
import { useNavigate } from "react-router-dom";
import type { Projetshort } from "../../types/projet";
export default function ProjetSection() {
const { t } = useTranslation();
const navigate = useNavigate();
const { data: apiprojet, isLoading } = useProjets(3);
// const { data: apiprojet, isLoading } = useProjets(3);
const apiprojet = {
projets: [
{
id: 1,
name: "protfolio",
shortdescriptionfr:
"Un portfolio personnel développé avec React et Material-UI, mettant en avant mes compétences, expériences et projets de manière moderne et responsive. avec une API en Go pour la gestion des données.",
technologies: ["React", "Node.js", "Go", "SQL"],
},
{
id: 2,
name: "Projet B",
shortdescriptionfr: "Description courte du projet B",
technologies: ["Go", "MongoDB"],
},
{
id: 3,
name: "VHS Video Home Share",
shortdescriptionfr:
"VHS est une plateforme web interactive qui transforme le streaming solitaire en une expérience collective. En mélangeant nostalgie et technologie moderne, l'application recrée la convivialité des cinémas et des soirées télé d'autrefois.",
technologies: ["php", "javascript", "twig", "SQL"],
},
],
};
const isLoading = false;
return (
<Box component="section">
<Typography
@@ -44,7 +70,7 @@ export default function ProjetSection() {
/>
))
: null)}
<Button
{/* <Button
variant="contained"
sx={{ backgroundColor: "#7ab2cb", mb: 4 }}
onClick={() => {
@@ -54,7 +80,7 @@ export default function ProjetSection() {
<Typography sx={{ textDecoration: "none" }}>
{t("SEE_ALL_PROJECTS")}
</Typography>
</Button>
</Button> */}
</Stack>
</Box>
);
+96
View File
@@ -0,0 +1,96 @@
import { Box, Container, Stack, Typography } from "@mui/material";
import { useTranslation } from "react-i18next";
// Direct import is the "gold standard" for MUI v5 to avoid Type Overload issues
import Grid from "@mui/material/Grid";
const skillCategories = [
{
title: "Front-end",
skills: ["React", "TypeScript", "HTML", "CSS", "Material UI", "Tailwind"],
},
{
title: "Back-end & BDD",
skills: ["Node.js", "Go", "Express", "MongoDB", "PostgreSQL"],
},
{
title: "Outils",
skills: ["Git", "Docker"],
},
];
export default function SkillsSection() {
const { t } = useTranslation();
const primaryBlue = "#7ab2cb";
return (
<Box sx={{ py: 8, bgcolor: "#fafbfc" }}>
<Typography
variant="h2"
id="SKILLS"
sx={{
bgcolor: "#d8d7d7",
py: 1,
mb: 4,
borderLeft: "10px solid #d8d7d7",
}}
>
{t("SKILLS")}
</Typography>
<Container maxWidth="lg">
<Stack spacing={4}>
<Grid container spacing={5} sx={{ mt: 2 }}>
{skillCategories.map((category, index) => (
<Grid item xs={12} md={4} key={index}>
<Box>
<Typography
variant="h6"
sx={{
mb: 3,
color: "#444",
fontWeight: 600,
borderBottom: `3px solid ${primaryBlue}`,
display: "inline-block",
pb: 0.5,
}}
>
{category.title}
</Typography>
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 1.5 }}>
{category.skills.map((s) => (
<Box
key={s}
sx={{
px: 2.5,
py: 1,
bgcolor: "white",
borderRadius: 2,
border: "1px solid #eaeaea",
color: "#555",
fontSize: "0.95rem",
fontWeight: 500,
boxShadow: "0 2px 6px rgba(0,0,0,0.02)",
transition: "all 0.2s ease-in-out",
cursor: "default",
"&:hover": {
borderColor: primaryBlue,
color: primaryBlue,
bgcolor: "rgba(122, 178, 203, 0.05)",
transform: "translateY(-2px)",
boxShadow: "0 6px 15px rgba(122, 178, 203, 0.15)",
},
}}
>
{s}
</Box>
))}
</Box>
</Box>
</Grid>
))}
</Grid>
</Stack>
</Container>
</Box>
);
}
+12 -1
View File
@@ -15,7 +15,10 @@ import React from "react";
const pages = [
{ name: "ABOUT_ME", path: "/#ABOUT_ME" },
{ name: "SKILLS", path: "/#SKILLS" },
{ name: "MY_PROJECTS", path: "/#MY_PROJECTS" },
{ name: "MY_EXPERIENCE", path: "/#MY_EXPERIENCE" },
{ name: "CV", path: "/#CV" },
];
export default function NavBar() {
@@ -28,11 +31,19 @@ export default function NavBar() {
setAnchorElNav(null);
};
return (
<AppBar position="static" sx={{ backgroundColor: "#7ab2cb" }}>
<AppBar
position="sticky"
sx={{
backgroundColor: "#7ab2cb",
top: 0,
zIndex: (theme) => theme.zIndex.appBar,
}}
>
<Container>
<Toolbar disableGutters>
<Typography
variant="h6"
content="h1"
noWrap
sx={{
mr: 2,