Compare commits

..

10 Commits

Author SHA1 Message Date
Van Leemput Dayron
59c1b8c8ad feat: Add high school education details to the Education component and include corresponding translations. 2025-12-01 15:01:39 +01:00
Van Leemput Dayron
1318b357b8 docs: Update contact details and footer in README, and ignore EMAILJS_SETUP.md. 2025-12-01 14:54:52 +01:00
Van Leemput Dayron
806ef5e1d8 feat: add Shelbys Bar project with new translations and online status. 2025-11-26 18:01:17 +01:00
Dayron
f4a5a6e396 fix: update favicon and site title; remove unused responsive language text 2025-11-12 23:48:01 +01:00
Dayron
16d5a224eb fix: adjust margin for hero title for better spacing 2025-11-12 20:48:30 +01:00
Dayron
6d26784241 feat: center social links on medium screens 2025-11-12 20:42:20 +01:00
Dayron
15e200dfab Implement code changes to enhance functionality and improve performance 2025-11-12 20:40:18 +01:00
Dayron
5be7a0c821 feat: add unique keys to motion components for improved rendering 2025-11-12 18:51:25 +01:00
Dayron
e8722cf25d feat: add language context and translation support
- Introduced LanguageProvider to manage language state and translations.
- Updated components (Header, Hero, About, Skills, Projects, Education, Contact) to utilize translations.
- Added language toggle button in the header for switching between French and English.
- Enhanced styling for language toggle button in the header.
2025-11-12 18:36:51 +01:00
Dayron
a4b4423ff4 feat: update portfolio with new contact form functionality and improved styling
- Removed outdated CV file and replaced with a new image for the profile.
- Implemented a new email service using EmailJS for contact form submissions.
- Enhanced the contact form to handle errors and success messages.
- Updated the About section for grammatical accuracy.
- Modified the Hero component to link to the new CV file and updated GitHub profile link.
- Updated project links to point to the correct GitHub repositories.
- Improved styling for error messages and avatar image with hover effects.
2025-11-12 00:47:02 +01:00
25 changed files with 2233 additions and 189 deletions

2
.gitignore vendored
View File

@@ -22,3 +22,5 @@ dist-ssr
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
.env
EMAILJS_SETUP.md

105
EMAILJS_SETUP.md Normal file
View File

@@ -0,0 +1,105 @@
# Configuration EmailJS pour le Portfolio
Ce guide vous explique comment configurer EmailJS pour que le formulaire de contact de votre portfolio fonctionne réellement.
## 1. Créer un compte EmailJS
1. Allez sur [EmailJS.com](https://www.emailjs.com/)
2. Créez un compte gratuit (jusqu'à 200 emails/mois)
3. Connectez-vous à votre dashboard
## 2. Configurer le service email
1. Dans votre dashboard EmailJS, allez dans **Email Services**
2. Cliquez sur **Add New Service**
3. Choisissez votre fournisseur email (Gmail, Outlook, etc.)
4. Suivez les instructions pour connecter votre compte email
5. Notez votre **Service ID** (ex: `service_xyz123`)
## 3. Créer un template d'email
1. Allez dans **Email Templates**
2. Cliquez sur **Create New Template**
3. Configurez votre template avec ces variables :
- `{{from_name}}` - Nom de l'expéditeur
- `{{from_email}}` - Email de l'expéditeur
- `{{subject}}` - Sujet du message
- `{{message}}` - Corps du message
Exemple de template :
```
Nouveau message depuis votre portfolio
De: {{from_name}} ({{from_email}})
Sujet: {{subject}}
Message:
{{message}}
---
Ce message a été envoyé depuis votre formulaire de contact.
```
4. Notez votre **Template ID** (ex: `template_abc456`)
## 4. Obtenir votre clé publique
1. Allez dans **Account** > **General**
2. Trouvez votre **Public Key** (ex: `user_def789`)
## 5. Configurer les variables d'environnement
Créez un fichier `.env` à la racine de votre projet :
```env
VITE_EMAILJS_SERVICE_ID=your_service_id_here
VITE_EMAILJS_TEMPLATE_ID=your_template_id_here
VITE_EMAILJS_PUBLIC_KEY=your_public_key_here
```
## 6. Sécurité et limitations
### Version gratuite d'EmailJS :
- 200 emails par mois maximum
- Pas de limitation de domaine
- Support communautaire
### Pour la production :
- Considérez un plan payant pour plus d'emails
- Ajoutez une validation côté serveur si nécessaire
- Implémentez un captcha pour éviter le spam
## 7. Test du formulaire
1. Remplissez les variables d'environnement
2. Redémarrez votre serveur de développement
3. Testez le formulaire de contact
4. Vérifiez la réception de l'email
## 8. Dépannage
### Erreurs communes :
**"Invalid template ID"**
- Vérifiez que le Template ID est correct
- Assurez-vous que le template est publié
**"Forbidden"**
- Vérifiez votre Public Key
- Assurez-vous que le service est actif
**Email non reçu**
- Vérifiez vos spams
- Vérifiez la configuration du service email
- Testez depuis le dashboard EmailJS
### Fallback automatique
Si EmailJS ne fonctionne pas, le système bascule automatiquement vers `mailto:` pour ouvrir le client email par défaut de l'utilisateur.
## 9. Fichiers modifiés
- `src/services/emailService.ts` - Service d'envoi d'emails
- `src/components/Contact.tsx` - Intégration du service
- `src/styles/components/_contact.scss` - Styles des messages d'erreur
Le formulaire de contact est maintenant prêt à fonctionner avec une vraie logique d'envoi d'emails !

View File

@@ -144,10 +144,10 @@ Ce projet est sous licence MIT. Voir le fichier `LICENSE` pour plus de détails.
**Dayron Van Leemput** **Dayron Van Leemput**
- 🎓 Étudiant en Technologies de l'Informatique - HELHa Tournai - 🎓 Étudiant en Technologies de l'Informatique - HELHa Tournai
- 💼 [LinkedIn](https://linkedin.com/in/dayronvanleemput) - 💼 [LinkedIn](https://www.linkedin.com/in/dayron-van-leemput-992a94398)
- 🐱 [GitHub](https://github.com/dayronvanleemput) - 🐱 [GitHub](https://github.com/Dayron-HELHa)
- 📧 Email : dayron.vanleemput@example.com - 📧 Email : dev.dayronvl@gmail.com
--- ---
*Développé avec ❤️ par Dayron Van Leemput - Novembre 2025* *Développé par Dayron Van Leemput - Novembre 2025*

View File

@@ -2,9 +2,9 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/public/personnes.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>xeewy.eu</title> <title>xeewy.be</title>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

10
package-lock.json generated
View File

@@ -8,6 +8,7 @@
"name": "xeewy.eu", "name": "xeewy.eu",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@emailjs/browser": "^4.4.1",
"framer-motion": "^12.23.24", "framer-motion": "^12.23.24",
"lucide-react": "^0.553.0", "lucide-react": "^0.553.0",
"react": "^19.2.0", "react": "^19.2.0",
@@ -312,6 +313,15 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@emailjs/browser": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/@emailjs/browser/-/browser-4.4.1.tgz",
"integrity": "sha512-DGSlP9sPvyFba3to2A50kDtZ+pXVp/0rhmqs2LmbMS3I5J8FSOgLwzY2Xb4qfKlOVHh29EAutLYwe5yuEZmEFg==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@esbuild/aix-ppc64": { "node_modules/@esbuild/aix-ppc64": {
"version": "0.25.12", "version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",

View File

@@ -10,6 +10,7 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@emailjs/browser": "^4.4.1",
"framer-motion": "^12.23.24", "framer-motion": "^12.23.24",
"lucide-react": "^0.553.0", "lucide-react": "^0.553.0",
"react": "^19.2.0", "react": "^19.2.0",

File diff suppressed because it is too large Load Diff

BIN
public/personnes.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,4 +1,5 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { LanguageProvider } from './contexts/LanguageContext';
import Header from './components/Header'; import Header from './components/Header';
import Hero from './components/Hero'; import Hero from './components/Hero';
import About from './components/About'; import About from './components/About';
@@ -31,17 +32,19 @@ function App() {
}; };
return ( return (
<div className={`app ${darkMode ? 'dark' : 'light'}`}> <LanguageProvider>
<Header darkMode={darkMode} toggleDarkMode={toggleDarkMode} /> <div className={`app ${darkMode ? 'dark' : 'light'}`}>
<main> <Header darkMode={darkMode} toggleDarkMode={toggleDarkMode} />
<Hero /> <main>
<About /> <Hero />
<Skills /> <About />
<Projects /> <Skills />
<Education /> <Projects />
<Contact /> <Education />
</main> <Contact />
</div> </main>
</div>
</LanguageProvider>
); );
} }

BIN
src/assets/dvl.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -1,12 +1,15 @@
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { User, Heart, Target, Coffee } from 'lucide-react'; import { User, Heart, Target, Coffee } from 'lucide-react';
import { useLanguage } from '../contexts/LanguageContext';
const About = () => { const About = () => {
const { t } = useLanguage();
const stats = [ const stats = [
{ icon: <User size={24} />, value: "3ème", label: "Année d'études" }, { icon: <User size={24} />, value: t('about.stats.year'), label: t('about.stats.yearLabel') },
{ icon: <Heart size={24} />, value: "100%", label: "Passion" }, { icon: <Heart size={24} />, value: t('about.stats.passion'), label: t('about.stats.passionLabel') },
{ icon: <Target size={24} />, value: "∞", label: "Objectifs" }, { icon: <Target size={24} />, value: t('about.stats.goals'), label: t('about.stats.goalsLabel') },
{ icon: <Coffee size={24} />, value: "☕", label: "Fuel quotidien" } { icon: <Coffee size={24} />, value: t('about.stats.fuel'), label: t('about.stats.fuelLabel') }
]; ];
const containerVariants = { const containerVariants = {
@@ -36,20 +39,22 @@ const About = () => {
<section id="about" className="about"> <section id="about" className="about">
<div className="container"> <div className="container">
<motion.div <motion.div
key="about-header"
className="section-header" className="section-header"
initial={{ opacity: 0, y: 50 }} initial={{ opacity: 0, y: 50 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }} transition={{ duration: 0.8 }}
viewport={{ once: true }} viewport={{ once: true }}
> >
<h2 className="section-title">À propos de moi</h2> <h2 className="section-title">{t('about.title')}</h2>
<p className="section-subtitle"> <p className="section-subtitle">
Découvrez qui je suis et ce qui me passionne {t('about.subtitle')}
</p> </p>
</motion.div> </motion.div>
<div className="about-content"> <div className="about-content">
<motion.div <motion.div
key="about-text"
className="about-text" className="about-text"
variants={containerVariants} variants={containerVariants}
initial="hidden" initial="hidden"
@@ -57,37 +62,29 @@ const About = () => {
viewport={{ once: true }} viewport={{ once: true }}
> >
<motion.div className="about-card" variants={itemVariants}> <motion.div className="about-card" variants={itemVariants}>
<h3>Mon parcours</h3> <h3>{t('about.journey.title')}</h3>
<p> <p>
Actuellement en 3ème année de <strong>Technologies de l'Informatique</strong> à la {t('about.journey.content')}
HELHa de Tournai, je me passionne pour le développement d'applications et les
nouvelles technologies. Mon parcours m'a permis d'acquérir une solide base
technique et une approche méthodique du développement.
</p> </p>
</motion.div> </motion.div>
<motion.div className="about-card" variants={itemVariants}> <motion.div className="about-card" variants={itemVariants}>
<h3>Ma passion</h3> <h3>{t('about.passion.title')}</h3>
<p> <p>
Ce qui m'anime le plus, c'est la création d'solutions innovantes qui résolvent {t('about.passion.content')}
des problèmes réels. J'aime particulièrement le développement mobile avec
<strong> Flutter</strong> et le développement web moderne avec
<strong> React</strong> et <strong> TypeScript</strong>.
</p> </p>
</motion.div> </motion.div>
<motion.div className="about-card" variants={itemVariants}> <motion.div className="about-card" variants={itemVariants}>
<h3>Mes objectifs</h3> <h3>{t('about.goals.title')}</h3>
<p> <p>
Je cherche constamment à améliorer mes compétences et à rester à jour avec {t('about.goals.content')}
les dernières tendances technologiques. Mon objectif est de devenir un
développeur full-stack polyvalent et de contribuer à des projets qui ont
un impact positif.
</p> </p>
</motion.div> </motion.div>
</motion.div> </motion.div>
<motion.div <motion.div
key="about-stats"
className="about-stats" className="about-stats"
variants={containerVariants} variants={containerVariants}
initial="hidden" initial="hidden"
@@ -96,7 +93,7 @@ const About = () => {
> >
{stats.map((stat, index) => ( {stats.map((stat, index) => (
<motion.div <motion.div
key={index} key={`stat-${index}`}
className="stat-card" className="stat-card"
variants={itemVariants} variants={itemVariants}
whileHover={{ whileHover={{
@@ -116,6 +113,7 @@ const About = () => {
</div> </div>
<motion.div <motion.div
key="about-highlight"
className="about-highlight" className="about-highlight"
initial={{ opacity: 0, scale: 0.9 }} initial={{ opacity: 0, scale: 0.9 }}
whileInView={{ opacity: 1, scale: 1 }} whileInView={{ opacity: 1, scale: 1 }}
@@ -123,13 +121,11 @@ const About = () => {
viewport={{ once: true }} viewport={{ once: true }}
> >
<div className="highlight-content"> <div className="highlight-content">
<h3>En quelques mots</h3> <h3>{t('about.quote.title')}</h3>
<p> <p>
"La technologie n'est rien. Ce qui est important, c'est d'avoir la foi en les gens, "{t('about.quote.content')}"
qu'ils soient fondamentalement bons et intelligents, et si vous leur donnez des outils,
ils feront des choses merveilleuses avec."
</p> </p>
<cite>- Steve Jobs</cite> <cite>- {t('about.quote.author')}</cite>
</div> </div>
</motion.div> </motion.div>
</div> </div>

View File

@@ -1,8 +1,12 @@
import { useState } from 'react'; import { useState } from 'react';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { Mail, Phone, MapPin, Send, Github, Linkedin, MessageCircle, CheckCircle } from 'lucide-react'; import { Mail, Phone, MapPin, Send, Github, Linkedin, MessageCircle, CheckCircle, AlertCircle } from 'lucide-react';
import { sendContactEmail } from '../services/emailService';
import type { ContactFormData } from '../services/emailService';
import { useLanguage } from '../contexts/LanguageContext';
const Contact = () => { const Contact = () => {
const { t } = useLanguage();
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
name: '', name: '',
email: '', email: '',
@@ -11,6 +15,7 @@ const Contact = () => {
}); });
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const [isSubmitted, setIsSubmitted] = useState(false); const [isSubmitted, setIsSubmitted] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => { const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const { name, value } = e.target; const { name, value } = e.target;
@@ -23,32 +28,51 @@ const Contact = () => {
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
setIsSubmitting(true); setIsSubmitting(true);
setError(null);
// Simulation d'envoi (remplacez par votre logique d'envoi réelle) try {
setTimeout(() => { const contactData: ContactFormData = {
name: formData.name,
email: formData.email,
subject: formData.subject,
message: formData.message
};
const result = await sendContactEmail(contactData);
if (result.success) {
setIsSubmitted(true);
setFormData({ name: '', email: '', subject: '', message: '' });
// Reset du message de succès après 5 secondes
setTimeout(() => {
setIsSubmitted(false);
}, 5000);
} else {
setError(result.message);
}
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Une erreur est survenue lors de l\'envoi du message.';
setError(errorMessage);
console.error('Erreur lors de l\'envoi de l\'email:', err);
} finally {
setIsSubmitting(false); setIsSubmitting(false);
setIsSubmitted(true); }
setFormData({ name: '', email: '', subject: '', message: '' });
// Reset du message de succès après 5 secondes
setTimeout(() => {
setIsSubmitted(false);
}, 5000);
}, 2000);
}; };
const contactInfo = [ const contactInfo = [
{ {
icon: <Mail size={24} />, icon: <Mail size={24} />,
title: "Email", title: "Email",
content: "dayronvanleemput@gmail.com", // Remplacez par votre email content: "dayronvanleemput@gmail.com",
link: "mailto:dayronvanleemput@gmail.com", link: "mailto:dayronvanleemput@gmail.com",
color: "#EA4335" color: "#EA4335"
}, },
{ {
icon: <Phone size={24} />, icon: <Phone size={24} />,
title: "Téléphone", title: "Téléphone",
content: "+32 455 19 47 62", // Remplacez par votre numéro content: "+32 455 19 47 62",
link: "tel:+32455194762", link: "tel:+32455194762",
color: "#34A853" color: "#34A853"
}, },
@@ -71,7 +95,7 @@ const Contact = () => {
{ {
icon: <Linkedin size={24} />, icon: <Linkedin size={24} />,
name: "LinkedIn", name: "LinkedIn",
url: "https://linkedin.com/in/dayronvanleemput", // Remplacez par votre profil url: "https://www.linkedin.com/in/dayron-van-leemput-992a94398", // Remplacez par votre profil
color: "#0077B5" color: "#0077B5"
} }
]; ];
@@ -103,19 +127,21 @@ const Contact = () => {
<section id="contact" className="contact"> <section id="contact" className="contact">
<div className="container"> <div className="container">
<motion.div <motion.div
key="contact-header"
className="section-header" className="section-header"
initial={{ opacity: 0, y: 50 }} initial={{ opacity: 0, y: 50 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }} transition={{ duration: 0.8 }}
viewport={{ once: true }} viewport={{ once: true }}
> >
<h2 className="section-title">Contactez-moi</h2> <h2 className="section-title">{t('contact.title')}</h2>
<p className="section-subtitle"> <p className="section-subtitle">
Une question, un projet ou simplement envie d'échanger ? N'hésitez pas à me contacter ! {t('contact.subtitle')}
</p> </p>
</motion.div> </motion.div>
<motion.div <motion.div
key="contact-content"
className="contact-content" className="contact-content"
variants={containerVariants} variants={containerVariants}
initial="hidden" initial="hidden"
@@ -127,12 +153,10 @@ const Contact = () => {
<div className="contact-intro"> <div className="contact-intro">
<h3> <h3>
<MessageCircle size={24} /> <MessageCircle size={24} />
Restons en contact {t('contact.stayInTouch')}
</h3> </h3>
<p> <p>
Je suis toujours intéressé par de nouveaux projets, des collaborations {t('contact.intro')}
ou simplement des discussions autour de la technologie. N'hésitez pas
à me contacter !
</p> </p>
</div> </div>
@@ -167,7 +191,7 @@ const Contact = () => {
</div> </div>
<div className="social-links"> <div className="social-links">
<h4>Retrouvez-moi aussi sur :</h4> <h4>{t('contact.findMeOn')}</h4>
<div className="social-grid"> <div className="social-grid">
{socialLinks.map((social, index) => ( {socialLinks.map((social, index) => (
<motion.a <motion.a
@@ -201,7 +225,7 @@ const Contact = () => {
{/* Formulaire de contact */} {/* Formulaire de contact */}
<motion.div className="contact-form-container" variants={itemVariants}> <motion.div className="contact-form-container" variants={itemVariants}>
<h3>Envoyez-moi un message</h3> <h3>{t('contact.sendMessage')}</h3>
{isSubmitted && ( {isSubmitted && (
<motion.div <motion.div
@@ -211,7 +235,19 @@ const Contact = () => {
exit={{ opacity: 0, scale: 0.8 }} exit={{ opacity: 0, scale: 0.8 }}
> >
<CheckCircle size={20} /> <CheckCircle size={20} />
Message envoyé avec succès ! Je vous répondrai bientôt. {t('contact.success')}
</motion.div>
)}
{error && (
<motion.div
className="error-message"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.8 }}
>
<AlertCircle size={20} />
{error}
</motion.div> </motion.div>
)} )}
@@ -224,7 +260,7 @@ const Contact = () => {
transition={{ duration: 0.5, delay: 0.1 }} transition={{ duration: 0.5, delay: 0.1 }}
viewport={{ once: true }} viewport={{ once: true }}
> >
<label htmlFor="name">Nom complet</label> <label htmlFor="name">{t('contact.form.name')}</label>
<input <input
type="text" type="text"
id="name" id="name"
@@ -232,7 +268,7 @@ const Contact = () => {
value={formData.name} value={formData.name}
onChange={handleChange} onChange={handleChange}
required required
placeholder="Votre nom" placeholder={t('contact.form.name')}
/> />
</motion.div> </motion.div>
@@ -243,7 +279,7 @@ const Contact = () => {
transition={{ duration: 0.5, delay: 0.2 }} transition={{ duration: 0.5, delay: 0.2 }}
viewport={{ once: true }} viewport={{ once: true }}
> >
<label htmlFor="email">Email</label> <label htmlFor="email">{t('contact.form.email')}</label>
<input <input
type="email" type="email"
id="email" id="email"
@@ -263,7 +299,7 @@ const Contact = () => {
transition={{ duration: 0.5, delay: 0.3 }} transition={{ duration: 0.5, delay: 0.3 }}
viewport={{ once: true }} viewport={{ once: true }}
> >
<label htmlFor="subject">Sujet</label> <label htmlFor="subject">{t('contact.form.subject')}</label>
<input <input
type="text" type="text"
id="subject" id="subject"
@@ -271,7 +307,7 @@ const Contact = () => {
value={formData.subject} value={formData.subject}
onChange={handleChange} onChange={handleChange}
required required
placeholder="Objet de votre message" placeholder={t('contact.form.subject')}
/> />
</motion.div> </motion.div>
@@ -282,7 +318,7 @@ const Contact = () => {
transition={{ duration: 0.5, delay: 0.4 }} transition={{ duration: 0.5, delay: 0.4 }}
viewport={{ once: true }} viewport={{ once: true }}
> >
<label htmlFor="message">Message</label> <label htmlFor="message">{t('contact.form.message')}</label>
<textarea <textarea
id="message" id="message"
name="message" name="message"
@@ -290,7 +326,7 @@ const Contact = () => {
onChange={handleChange} onChange={handleChange}
required required
rows={6} rows={6}
placeholder="Votre message..." placeholder={t('contact.form.message')}
/> />
</motion.div> </motion.div>
@@ -308,12 +344,12 @@ const Contact = () => {
{isSubmitting ? ( {isSubmitting ? (
<> <>
<div className="loading-spinner" /> <div className="loading-spinner" />
Envoi en cours... {t('contact.form.sending')}
</> </>
) : ( ) : (
<> <>
<Send size={20} /> <Send size={20} />
Envoyer le message {t('contact.form.send')}
</> </>
)} )}
</motion.button> </motion.button>
@@ -330,7 +366,7 @@ const Contact = () => {
viewport={{ once: true }} viewport={{ once: true }}
> >
<p> <p>
© 2025 Dayron Van Leemput. Développé avec ❤️ en React et TypeScript. © 2025 Dayron Van Leemput.
</p> </p>
</motion.footer> </motion.footer>
</div> </div>

View File

@@ -1,42 +1,61 @@
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { GraduationCap, Calendar, MapPin, Award, BookOpen, Target } from 'lucide-react'; import { GraduationCap, Calendar, MapPin, Award, BookOpen, Target } from 'lucide-react';
import { useLanguage } from '../contexts/LanguageContext';
const Education = () => { const Education = () => {
const { t } = useLanguage();
const education = [ const education = [
{ {
id: 1, id: 1,
degree: "Bachelier en Technologies de l'Informatique", degree: t('education.degree'),
school: "HELHa - Haute École Louvain en Hainaut", school: t('education.school'),
location: "Tournai, Belgique", location: t('education.location'),
period: "2023 - 2026", period: t('education.period'),
currentYear: "3ème année", currentYear: t('education.currentYear'),
status: "En cours", status: t('education.status'),
description: "Formation complète en développement logiciel, programmation, bases de données, réseaux et gestion de projets informatiques.", description: t('education.description'),
highlights: [ highlights: [
"Programmation orientée objet (Java, C#)", t('education.highlights.0'),
"Développement web (HTML, CSS, JavaScript, React)", t('education.highlights.1'),
"Développement mobile (Flutter, Dart)", t('education.highlights.2'),
"Bases de données et SQL", t('education.highlights.3'),
"Gestion de projets", t('education.highlights.4'),
"Réseaux et systèmes" t('education.highlights.5')
], ],
color: "#4CAF50", color: "#4CAF50",
icon: <GraduationCap size={24} /> icon: <GraduationCap size={24} />
},
{
id: 2,
degree: t('education.highschool.degree'),
school: t('education.highschool.school'),
location: t('education.highschool.location'),
period: t('education.highschool.period'),
currentYear: "",
status: t('education.highschool.status'),
description: t('education.highschool.description'),
highlights: [
t('education.highschool.highlights.0'),
t('education.highschool.highlights.1'),
t('education.highschool.highlights.2')
],
color: "#3F51B5",
icon: <GraduationCap size={24} />
} }
]; ];
const certifications = [ const certifications = [
{ {
title: "Développement Mobile Flutter", title: t('education.cert1.title'),
provider: "Formation autodidacte", provider: t('education.cert1.provider'),
date: "2024", date: t('education.cert1.date'),
skills: ["Dart", "Flutter", "Firebase", "API REST"], skills: ["Dart", "Flutter", "Firebase", "API REST"],
color: "#2196F3" color: "#2196F3"
}, },
{ {
title: "React & TypeScript", title: t('education.cert2.title'),
provider: "Projets personnels", provider: t('education.cert2.provider'),
date: "2024", date: t('education.cert2.date'),
skills: ["React", "TypeScript", "Hooks", "Context API"], skills: ["React", "TypeScript", "Hooks", "Context API"],
color: "#FF9800" color: "#FF9800"
} }
@@ -69,21 +88,23 @@ const Education = () => {
<section id="education" className="education"> <section id="education" className="education">
<div className="container"> <div className="container">
<motion.div <motion.div
key="education-header"
className="section-header" className="section-header"
initial={{ opacity: 0, y: 50 }} initial={{ opacity: 0, y: 50 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }} transition={{ duration: 0.8 }}
viewport={{ once: true }} viewport={{ once: true }}
> >
<h2 className="section-title">Formation & Apprentissage</h2> <h2 className="section-title">{t('education.title')}</h2>
<p className="section-subtitle"> <p className="section-subtitle">
Mon parcours académique et mes apprentissages continus {t('education.subtitle')}
</p> </p>
</motion.div> </motion.div>
<div className="education-content"> <div className="education-content">
{/* Formation principale */} {/* Formation principale */}
<motion.div <motion.div
key="education-main"
className="education-main" className="education-main"
variants={containerVariants} variants={containerVariants}
initial="hidden" initial="hidden"
@@ -178,7 +199,7 @@ const Education = () => {
> >
<h3 className="certifications-title"> <h3 className="certifications-title">
<Award size={24} /> <Award size={24} />
Formations complémentaires & Autodidacte {t('education.certifications.title')}
</h3> </h3>
<div className="certifications-grid"> <div className="certifications-grid">
{certifications.map((cert, index) => ( {certifications.map((cert, index) => (
@@ -224,13 +245,13 @@ const Education = () => {
transition={{ duration: 0.8, delay: 0.4 }} transition={{ duration: 0.8, delay: 0.4 }}
viewport={{ once: true }} viewport={{ once: true }}
> >
<h3>Objectifs d'apprentissage 2025</h3> <h3>{t('education.learningGoals2025')}</h3>
<div className="goals-grid"> <div className="goals-grid">
{[ {[
{ goal: "Maîtriser Firebase et les services cloud", progress: 60 }, { goal: t('education.goal1'), progress: 60 },
{ goal: "Approfondir Spring Boot pour le backend", progress: 30 }, { goal: t('education.goal2'), progress: 30 },
{ goal: "Apprendre Docker et les conteneurs", progress: 20 }, { goal: t('education.goal3'), progress: 20 },
{ goal: "Développer mes compétences en UI/UX", progress: 45 } { goal: t('education.goal4'), progress: 45 }
].map((item, index) => ( ].map((item, index) => (
<motion.div <motion.div
key={index} key={index}

View File

@@ -1,6 +1,7 @@
import { useState } from 'react'; import { useState } from 'react';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { Menu, X, Sun, Moon } from 'lucide-react'; import { Menu, X, Sun, Moon, Globe } from 'lucide-react';
import { useLanguage } from '../contexts/LanguageContext';
interface HeaderProps { interface HeaderProps {
darkMode: boolean; darkMode: boolean;
@@ -9,14 +10,15 @@ interface HeaderProps {
const Header = ({ darkMode, toggleDarkMode }: HeaderProps) => { const Header = ({ darkMode, toggleDarkMode }: HeaderProps) => {
const [isMenuOpen, setIsMenuOpen] = useState(false); const [isMenuOpen, setIsMenuOpen] = useState(false);
const { language, setLanguage, t } = useLanguage();
const menuItems = [ const menuItems = [
{ name: 'Accueil', href: '#hero' }, { id: 'home', name: t('nav.home'), href: '#hero' },
{ name: 'À propos', href: '#about' }, { id: 'about', name: t('nav.about'), href: '#about' },
{ name: 'Compétences', href: '#skills' }, { id: 'skills', name: t('nav.skills'), href: '#skills' },
{ name: 'Projets', href: '#projects' }, { id: 'projects', name: t('nav.projects'), href: '#projects' },
{ name: 'Formation', href: '#education' }, { id: 'education', name: t('nav.education'), href: '#education' },
{ name: 'Contact', href: '#contact' } { id: 'contact', name: t('nav.contact'), href: '#contact' }
]; ];
const scrollToSection = (href: string) => { const scrollToSection = (href: string) => {
@@ -27,6 +29,10 @@ const Header = ({ darkMode, toggleDarkMode }: HeaderProps) => {
setIsMenuOpen(false); setIsMenuOpen(false);
}; };
const toggleLanguage = () => {
setLanguage(language === 'fr' ? 'en' : 'fr');
};
return ( return (
<motion.header <motion.header
initial={{ y: -100 }} initial={{ y: -100 }}
@@ -49,7 +55,7 @@ const Header = ({ darkMode, toggleDarkMode }: HeaderProps) => {
<ul className="nav-menu desktop-menu"> <ul className="nav-menu desktop-menu">
{menuItems.map((item, index) => ( {menuItems.map((item, index) => (
<motion.li <motion.li
key={item.name} key={item.id}
initial={{ opacity: 0, y: -20 }} initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: index * 0.1 }} transition={{ duration: 0.5, delay: index * 0.1 }}
@@ -68,13 +74,26 @@ const Header = ({ darkMode, toggleDarkMode }: HeaderProps) => {
</ul> </ul>
<div className="nav-controls"> <div className="nav-controls">
{/* Toggle langue */}
<motion.button
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
onClick={toggleLanguage}
className="language-toggle"
aria-label={t('btn.changeLang')}
title={t('btn.changeLang')}
>
<Globe size={18} />
<span className="language-text">{language === 'fr' ? 'EN' : 'FR'}</span>
</motion.button>
{/* Toggle thème */} {/* Toggle thème */}
<motion.button <motion.button
whileHover={{ scale: 1.1 }} whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }} whileTap={{ scale: 0.9 }}
onClick={toggleDarkMode} onClick={toggleDarkMode}
className="theme-toggle" className="theme-toggle"
aria-label="Changer de thème" aria-label={t('btn.changeTheme')}
> >
{darkMode ? <Sun size={20} /> : <Moon size={20} />} {darkMode ? <Sun size={20} /> : <Moon size={20} />}
</motion.button> </motion.button>
@@ -85,7 +104,7 @@ const Header = ({ darkMode, toggleDarkMode }: HeaderProps) => {
whileTap={{ scale: 0.9 }} whileTap={{ scale: 0.9 }}
onClick={() => setIsMenuOpen(!isMenuOpen)} onClick={() => setIsMenuOpen(!isMenuOpen)}
className="menu-toggle" className="menu-toggle"
aria-label="Menu" aria-label={t('btn.menu')}
> >
{isMenuOpen ? <X size={24} /> : <Menu size={24} />} {isMenuOpen ? <X size={24} /> : <Menu size={24} />}
</motion.button> </motion.button>
@@ -99,7 +118,7 @@ const Header = ({ darkMode, toggleDarkMode }: HeaderProps) => {
transition={{ duration: 0.3 }} transition={{ duration: 0.3 }}
> >
{menuItems.map((item) => ( {menuItems.map((item) => (
<li key={item.name}> <li key={item.id}>
<a <a
href={item.href} href={item.href}
onClick={(e) => { onClick={(e) => {

View File

@@ -1,12 +1,16 @@
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { Download, Github, Linkedin, Mail } from 'lucide-react'; import { Download, Github, Linkedin, Mail } from 'lucide-react';
import { useLanguage } from '../contexts/LanguageContext';
import dvlPhoto from '../assets/dvl.jpg';
const Hero = () => { const Hero = () => {
const { t } = useLanguage();
const handleDownloadCV = () => { const handleDownloadCV = () => {
// Ici, vous pouvez ajouter le lien vers votre CV // Ici, vous pouvez ajouter le lien vers votre CV
const link = document.createElement('a'); const link = document.createElement('a');
link.href = '/cv-dayron-van-leemput.pdf'; // Ajoutez votre CV dans le dossier public link.href = '/Dayron_Van_Leemput_CV.pdf'; // Ajoutez votre CV dans le dossier public
link.download = 'CV-Dayron-Van-Leemput.pdf'; link.download = 'Dayron_Van_Leemput_CV.pdf';
link.click(); link.click();
}; };
@@ -14,6 +18,7 @@ const Hero = () => {
<section id="hero" className="hero"> <section id="hero" className="hero">
<div className="hero-content"> <div className="hero-content">
<motion.div <motion.div
key="hero-text"
className="hero-text" className="hero-text"
initial={{ opacity: 0, y: 50 }} initial={{ opacity: 0, y: 50 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
@@ -25,7 +30,7 @@ const Hero = () => {
animate={{ opacity: 1, x: 0 }} animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.8, delay: 0.4 }} transition={{ duration: 0.8, delay: 0.4 }}
> >
Dayron Van Leemput {t('hero.title')}
</motion.h1> </motion.h1>
<motion.h2 <motion.h2
@@ -34,7 +39,7 @@ const Hero = () => {
animate={{ opacity: 1, x: 0 }} animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.8, delay: 0.6 }} transition={{ duration: 0.8, delay: 0.6 }}
> >
Étudiant en Technologies de l'Informatique {t('hero.subtitle')}
</motion.h2> </motion.h2>
<motion.p <motion.p
@@ -43,8 +48,7 @@ const Hero = () => {
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.8 }} transition={{ duration: 0.8, delay: 0.8 }}
> >
Bac 3 à la HELHa de Tournai | Jeune développeur passionné par les nouvelles technologies {t('hero.description')}
et le développement d'applications innovantes
</motion.p> </motion.p>
<motion.div <motion.div
@@ -60,7 +64,7 @@ const Hero = () => {
whileTap={{ scale: 0.95 }} whileTap={{ scale: 0.95 }}
> >
<Download size={20} /> <Download size={20} />
Télécharger mon CV {t('btn.downloadCV')}
</motion.button> </motion.button>
<motion.a <motion.a
@@ -74,7 +78,7 @@ const Hero = () => {
whileTap={{ scale: 0.95 }} whileTap={{ scale: 0.95 }}
> >
<Mail size={20} /> <Mail size={20} />
Me contacter {t('btn.contactMe')}
</motion.a> </motion.a>
</motion.div> </motion.div>
@@ -85,7 +89,7 @@ const Hero = () => {
transition={{ duration: 0.8, delay: 1.2 }} transition={{ duration: 0.8, delay: 1.2 }}
> >
<motion.a <motion.a
href="https://github.com/dayronvanleemput" // Remplacez par votre profil GitHub href="https://github.com/Dayron-HELHa" // Remplacez par votre profil GitHub
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="social-link" className="social-link"
@@ -96,7 +100,7 @@ const Hero = () => {
</motion.a> </motion.a>
<motion.a <motion.a
href="https://linkedin.com/in/dayronvanleemput" // Remplacez par votre profil LinkedIn href="https://www.linkedin.com/in/dayron-van-leemput-992a94398" // Remplacez par votre profil LinkedIn
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="social-link" className="social-link"
@@ -109,6 +113,7 @@ const Hero = () => {
</motion.div> </motion.div>
<motion.div <motion.div
key="hero-image"
className="hero-image" className="hero-image"
initial={{ opacity: 0, scale: 0.8 }} initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }} animate={{ opacity: 1, scale: 1 }}
@@ -119,10 +124,11 @@ const Hero = () => {
whileHover={{ scale: 1.05, rotate: 5 }} whileHover={{ scale: 1.05, rotate: 5 }}
transition={{ type: "spring", stiffness: 300, damping: 10 }} transition={{ type: "spring", stiffness: 300, damping: 10 }}
> >
{/* Vous pouvez remplacer ceci par votre photo */} <img
<div className="avatar-placeholder"> src={dvlPhoto}
<span>DV</span> alt="Dayron Van Leemput - Portrait"
</div> className="avatar-image"
/>
</motion.div> </motion.div>
</motion.div> </motion.div>
</div> </div>

View File

@@ -1,45 +1,66 @@
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { ExternalLink, Github, MapPin } from 'lucide-react'; import { ExternalLink, Github, MapPin, Wine } from 'lucide-react';
import { useLanguage } from '../contexts/LanguageContext';
const Projects = () => { const Projects = () => {
const { t } = useLanguage();
const projects = [ const projects = [
{ {
id: 1, id: 1,
title: "Travel Mate", title: t('projects.travelMate.title'),
description: "Application mobile conçue pour simplifier l'organisation de voyages de groupe. Elle permet de centraliser toutes les informations importantes d'un voyage : planification, gestion des dépenses, découverte d'activités et coordination entre les participants.", description: t('projects.travelMate.description'),
status: "Bientôt disponible sur App Store et Play Store", status: t('projects.status.available'),
technologies: ["Dart", "Flutter", "Firebase"], technologies: ["Dart", "Flutter", "Firebase"],
features: [ features: [
"Planification de voyage collaborative", t('projects.travelMate.feature1'),
"Gestion des dépenses partagées", t('projects.travelMate.feature2'),
"Découverte d'activités locales", t('projects.travelMate.feature3'),
"Coordination en temps réel" t('projects.travelMate.feature4')
], ],
color: "#4CAF50", color: "#4CAF50",
icon: <MapPin size={24} />, icon: <MapPin size={24} />,
links: { links: {
github: "#", // Remplacez par votre lien GitHub github: "https://github.com/Dayron-HELHa/travel_mate", // Remplacez par votre lien GitHub
demo: "#" demo: "#"
}, },
image: "/travel-mate-preview.png" // Ajoutez votre image dans le dossier public image: "/travel-mate-preview.png" // Ajoutez votre image dans le dossier public
}, },
{
id: 3,
title: t('projects.shelbys.title'),
description: t('projects.shelbys.description'),
status: t('projects.status.online'),
technologies: ["React", "Vite", "TailwindCSS", "Framer Motion"], // Inferring stack based on modern standards and user's other projects
features: [
t('projects.shelbys.feature1'),
t('projects.shelbys.feature2'),
t('projects.shelbys.feature4')
],
color: "#E91E63", // A distinct color
icon: <Wine size={24} />,
links: {
github: "#", // Assuming private or not provided
demo: "https://shelbys.be"
},
image: "/shelbys-preview.png" // Placeholder, user might need to add this
},
{ {
id: 2, id: 2,
title: "Portfolio Web", title: t('projects.portfolio.title'),
description: "Site web personnel moderne et responsive développé avec React et TypeScript. Inclut des animations fluides, un mode sombre/clair et une architecture modulaire.", description: t('projects.portfolio.description'),
status: "Projet actuel", status: t('projects.status.current'),
technologies: ["React", "TypeScript", "Framer Motion", "CSS3"], technologies: ["React", "TypeScript", "Framer Motion", "CSS3"],
features: [ features: [
"Design responsive", t('projects.portfolio.feature1'),
"Animations fluides", t('projects.portfolio.feature2'),
"Mode sombre/clair", t('projects.portfolio.feature3'),
"Performance optimisée" t('projects.portfolio.feature4')
], ],
color: "#2196F3", color: "#2196F3",
icon: <ExternalLink size={24} />, icon: <ExternalLink size={24} />,
links: { links: {
github: "https://github.com/dayronvanleemput/portfolio", // Remplacez par votre lien github: "https://github.com/Dayron-HELHa/xeewy.eu", // Remplacez par votre lien
demo: "https://dayronvanleemput.dev" // Remplacez par votre lien demo: "https://xeewy.be" // Remplacez par votre lien
} }
} }
]; ];
@@ -71,19 +92,21 @@ const Projects = () => {
<section id="projects" className="projects"> <section id="projects" className="projects">
<div className="container"> <div className="container">
<motion.div <motion.div
key="projects-header"
className="section-header" className="section-header"
initial={{ opacity: 0, y: 50 }} initial={{ opacity: 0, y: 50 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }} transition={{ duration: 0.8 }}
viewport={{ once: true }} viewport={{ once: true }}
> >
<h2 className="section-title">Mes Projets</h2> <h2 className="section-title">{t('projects.title')}</h2>
<p className="section-subtitle"> <p className="section-subtitle">
Découvrez les projets sur lesquels j'ai travaillé et qui me tiennent à cœur {t('projects.subtitle')}
</p> </p>
</motion.div> </motion.div>
<motion.div <motion.div
key="projects-grid"
className="projects-grid" className="projects-grid"
variants={containerVariants} variants={containerVariants}
initial="hidden" initial="hidden"
@@ -138,7 +161,7 @@ const Projects = () => {
</div> </div>
<div className="project-features"> <div className="project-features">
<h4>Fonctionnalités principales :</h4> <h4>{t('projects.features')}</h4>
<ul> <ul>
{project.features.map((feature, featureIndex) => ( {project.features.map((feature, featureIndex) => (
<motion.li <motion.li
@@ -170,7 +193,7 @@ const Projects = () => {
whileTap={{ scale: 0.95 }} whileTap={{ scale: 0.95 }}
> >
<Github size={20} /> <Github size={20} />
Code {t('projects.btn.code')}
</motion.a> </motion.a>
)} )}
{project.links.demo !== "#" && ( {project.links.demo !== "#" && (
@@ -183,7 +206,7 @@ const Projects = () => {
whileTap={{ scale: 0.95 }} whileTap={{ scale: 0.95 }}
> >
<ExternalLink size={20} /> <ExternalLink size={20} />
Voir le projet {t('projects.btn.viewProject')}
</motion.a> </motion.a>
)} )}
</div> </div>

View File

@@ -1,11 +1,14 @@
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { Code, Database, Smartphone, Globe, Server, Wrench } from 'lucide-react'; import { Code, Database, Smartphone, Globe, Server, Wrench } from 'lucide-react';
import { useLanguage } from '../contexts/LanguageContext';
const Skills = () => { const Skills = () => {
const { t } = useLanguage();
const skillCategories = [ const skillCategories = [
{ {
id: 'mobile',
icon: <Smartphone size={32} />, icon: <Smartphone size={32} />,
title: "Mobile", title: t('skills.category.mobile'),
color: "#4FC3F7", color: "#4FC3F7",
skills: [ skills: [
{ name: "Dart", level: 85, color: "#0175C2" }, { name: "Dart", level: 85, color: "#0175C2" },
@@ -13,8 +16,9 @@ const Skills = () => {
] ]
}, },
{ {
id: 'frontend',
icon: <Globe size={32} />, icon: <Globe size={32} />,
title: "Frontend", title: t('skills.category.frontend'),
color: "#42A5F5", color: "#42A5F5",
skills: [ skills: [
{ name: "React", level: 75, color: "#61DAFB" }, { name: "React", level: 75, color: "#61DAFB" },
@@ -23,8 +27,9 @@ const Skills = () => {
] ]
}, },
{ {
id: 'backend',
icon: <Server size={32} />, icon: <Server size={32} />,
title: "Backend", title: t('skills.category.backend'),
color: "#66BB6A", color: "#66BB6A",
skills: [ skills: [
{ name: "Java", level: 75, color: "#ED8B00" }, { name: "Java", level: 75, color: "#ED8B00" },
@@ -32,8 +37,9 @@ const Skills = () => {
] ]
}, },
{ {
icon: <Database size={32} />, id: 'tools',
title: "Outils & Autres", icon: <Wrench size={32} />,
title: t('skills.category.tools'),
color: "#AB47BC", color: "#AB47BC",
skills: [ skills: [
{ name: "Git", level: 70, color: "#F05032" }, { name: "Git", level: 70, color: "#F05032" },
@@ -70,19 +76,21 @@ const Skills = () => {
<section id="skills" className="skills"> <section id="skills" className="skills">
<div className="container"> <div className="container">
<motion.div <motion.div
key="skills-header"
className="section-header" className="section-header"
initial={{ opacity: 0, y: 50 }} initial={{ opacity: 0, y: 50 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }} transition={{ duration: 0.8 }}
viewport={{ once: true }} viewport={{ once: true }}
> >
<h2 className="section-title">Compétences & Technologies</h2> <h2 className="section-title">{t('skills.title')}</h2>
<p className="section-subtitle"> <p className="section-subtitle">
Les technologies que je maîtrise et avec lesquelles j'aime travailler {t('skills.subtitle')}
</p> </p>
</motion.div> </motion.div>
<motion.div <motion.div
key="skills-grid"
className="skills-grid" className="skills-grid"
variants={containerVariants} variants={containerVariants}
initial="hidden" initial="hidden"
@@ -91,7 +99,7 @@ const Skills = () => {
> >
{skillCategories.map((category, categoryIndex) => ( {skillCategories.map((category, categoryIndex) => (
<motion.div <motion.div
key={category.title} key={category.id}
className="skill-category" className="skill-category"
variants={categoryVariants} variants={categoryVariants}
whileHover={{ whileHover={{
@@ -157,13 +165,13 @@ const Skills = () => {
transition={{ duration: 0.8, delay: 0.3 }} transition={{ duration: 0.8, delay: 0.3 }}
viewport={{ once: true }} viewport={{ once: true }}
> >
<h3 className="soft-skills-title">Autres compétences</h3> <h3 className="soft-skills-title">{t('skills.otherSkills')}</h3>
<div className="soft-skills-grid"> <div className="soft-skills-grid">
{[ {[
{ name: "Résolution de problèmes", icon: <Wrench size={20} /> }, { name: t('skills.problemSolving'), icon: <Wrench size={20} /> },
{ name: "Travail en équipe", icon: <Code size={20} /> }, { name: t('skills.teamwork'), icon: <Code size={20} /> },
{ name: "Apprentissage continu", icon: <Database size={20} /> }, { name: t('skills.continuousLearning'), icon: <Database size={20} /> },
{ name: "Communication", icon: <Globe size={20} /> } { name: t('skills.communication'), icon: <Globe size={20} /> }
].map((softSkill, index) => ( ].map((softSkill, index) => (
<motion.div <motion.div
key={softSkill.name} key={softSkill.name}

View File

@@ -0,0 +1,326 @@
// ========================
// CONTEXTE DE LANGUE
// ========================
import React, { createContext, useContext, useState } from 'react';
import type { ReactNode } from 'react';
// Types pour les langues supportées
export type Language = 'fr' | 'en';
// Interface pour le contexte
interface LanguageContextType {
language: Language;
setLanguage: (lang: Language) => void;
t: (key: string) => string;
}
// Textes traduits
const translations = {
fr: {
// Navigation
'nav.home': 'Accueil',
'nav.about': 'À propos',
'nav.skills': 'Compétences',
'nav.projects': 'Projets',
'nav.education': 'Formation',
'nav.contact': 'Contact',
// Boutons
'btn.changeTheme': 'Changer de thème',
'btn.menu': 'Menu',
'btn.changeLang': 'English',
'btn.downloadCV': 'Télécharger CV',
'btn.viewProjects': 'Voir mes projets',
'btn.contactMe': 'Me contacter',
// Hero
'hero.greeting': 'Salut, je suis',
'hero.title': 'Dayron Van Leemput',
'hero.subtitle': 'Étudiant en Technologies de l\'Informatique',
'hero.description': 'Bac 3 à la HELHa de Tournai | Jeune développeur passionné par les nouvelles technologies et le développement d\'applications innovantes',
// About
'about.title': 'À propos de moi',
'about.subtitle': 'Découvrez mon parcours et mes passions',
'about.stats.year': '3ème',
'about.stats.yearLabel': 'Année d\'études',
'about.stats.passion': '100%',
'about.stats.passionLabel': 'Passion',
'about.stats.goals': '∞',
'about.stats.goalsLabel': 'Objectifs',
'about.stats.fuel': '☕',
'about.stats.fuelLabel': 'Fuel quotidien',
'about.journey.title': 'Mon parcours',
'about.journey.content': 'Actuellement en 3ème année de Technologies de l\'Informatique à la HELHa de Tournai, je me passionne pour le développement d\'applications et les nouvelles technologies. Mon parcours m\'a permis d\'acquérir une solide base technique et une approche méthodique du développement.',
'about.passion.title': 'Ma passion',
'about.passion.content': 'Ce qui m\'anime le plus, c\'est la création de solutions innovantes qui résolvent des problèmes réels. J\'aime particulièrement le développement mobile avec Flutter et le développement web moderne avec React et TypeScript.',
'about.goals.title': 'Mes objectifs',
'about.goals.content': 'Je cherche constamment à améliorer mes compétences et à rester à jour avec les dernières tendances technologiques. Mon objectif est de devenir un développeur full-stack polyvalent et de contribuer à des projets qui ont un impact positif.',
'about.quote.title': 'En quelques mots',
'about.quote.content': 'La technologie n\'est rien. Ce qui est important, c\'est d\'avoir la foi en les gens, qu\'ils soient fondamentalement bons et intelligents, et si vous leur donnez des outils, ils feront des choses merveilleuses avec.',
'about.quote.author': 'Steve Jobs',
// Skills
'skills.title': 'Mes Compétences',
'skills.subtitle': 'Technologies et outils que je maîtrise',
'skills.category.mobile': 'Mobile',
'skills.category.frontend': 'Frontend',
'skills.category.backend': 'Backend',
'skills.category.tools': 'Outils & Autres',
'skills.otherSkills': 'Autres compétences',
'skills.problemSolving': 'Résolution de problèmes',
'skills.teamwork': 'Travail en équipe',
'skills.continuousLearning': 'Apprentissage continu',
'skills.communication': 'Communication',
// Projects
'projects.title': 'Mes Projets',
'projects.subtitle': 'Découvrez mes réalisations et mes expériences',
'projects.status.available': 'Bientôt disponible sur App Store et Play Store',
'projects.status.current': 'Projet actuel',
'projects.status.online': 'En ligne',
'projects.features': 'Fonctionnalités principales :',
'projects.btn.code': 'Code',
'projects.btn.viewProject': 'Voir le projet',
'projects.travelMate.title': 'Travel Mate',
'projects.travelMate.description': 'Application mobile conçue pour simplifier l\'organisation de voyages de groupe. Elle permet de centraliser toutes les informations importantes d\'un voyage : planification, gestion des dépenses, découverte d\'activités et coordination entre les participants.',
'projects.travelMate.feature1': 'Planification de voyage collaborative',
'projects.travelMate.feature2': 'Gestion des dépenses partagées',
'projects.travelMate.feature3': 'Découverte d\'activités locales',
'projects.travelMate.feature4': 'Coordination en temps réel',
'projects.portfolio.title': 'Portfolio Web',
'projects.portfolio.description': 'Site web personnel moderne et responsive développé avec React et TypeScript. Inclut des animations fluides, un mode sombre/clair et une architecture modulaire.',
'projects.portfolio.feature1': 'Design responsive',
'projects.portfolio.feature2': 'Animations fluides',
'projects.portfolio.feature3': 'Mode sombre/clair',
'projects.portfolio.feature4': 'Performance optimisée',
'projects.shelbys.title': 'Shelbys Bar',
'projects.shelbys.description': 'Site vitrine élégant pour le bar Shelbys Bar. Présente l\'ambiance, le menu et les événements du bar avec un design moderne et immersif.',
'projects.shelbys.feature1': 'Design moderne et immersif',
'projects.shelbys.feature2': 'Présentation du menu',
'projects.shelbys.feature4': 'Informations pratiques',
// Education
'education.title': 'Formation',
'education.subtitle': 'Mon parcours académique et professionnel',
'education.learningGoals2025': 'Objectifs d\'apprentissage 2025',
'education.goal1': 'Maîtriser Firebase et les services cloud',
'education.goal2': 'Approfondir Spring Boot pour le backend',
'education.goal3': 'Apprendre Docker et les conteneurs',
'education.goal4': 'Développer mes compétences en UI/UX',
'education.degree': 'Bachelier en Technologies de l\'Informatique',
'education.school': 'HELHa - Haute École Louvain en Hainaut',
'education.location': 'Tournai, Belgique',
'education.period': '2023 - 2026',
'education.currentYear': '3ème année',
'education.status': 'En cours',
'education.description': 'Formation complète en développement logiciel, programmation, bases de données, réseaux et gestion de projets informatiques.',
'education.highlights.0': 'Programmation orientée objet (Java, C#)',
'education.highlights.1': 'Développement web (HTML, CSS, JavaScript, React)',
'education.highlights.2': 'Développement mobile (Flutter, Dart)',
'education.highlights.3': 'Bases de données et SQL',
'education.highlights.4': 'Gestion de projets',
'education.highlights.5': 'Réseaux et systèmes',
'education.certifications.title': 'Certifications & Formations',
'education.cert1.title': 'Développement Mobile Flutter',
'education.cert1.provider': 'Formation autodidacte',
'education.cert1.date': '2024',
'education.cert2.title': 'React & TypeScript',
'education.cert2.provider': 'Projets personnels',
'education.cert2.date': '2024',
'education.highschool.degree': 'CESS (Certificat d\'Enseignement Secondaire Supérieur)',
'education.highschool.school': 'Athénée Royal d\'Ath',
'education.highschool.location': 'Ath, Belgique',
'education.highschool.period': '2016 - 2022',
'education.highschool.status': 'Diplômé',
'education.highschool.description': 'Section Math-Sciences (Math 8h - Sciences 7h)',
'education.highschool.highlights.0': 'Mathématiques avancées',
'education.highschool.highlights.1': 'Sciences (Physique, Chimie, Biologie)',
'education.highschool.highlights.2': 'Langues',
// Contact
'contact.title': 'Contactez-moi',
'contact.subtitle': 'Une question, un projet ou simplement envie d\'échanger ? N\'hésitez pas à me contacter !',
'contact.form.name': 'Nom complet',
'contact.form.email': 'Email',
'contact.form.subject': 'Sujet',
'contact.form.message': 'Message',
'contact.form.send': 'Envoyer le message',
'contact.form.sending': 'Envoi en cours...',
'contact.success': 'Message envoyé avec succès ! Je vous répondrai bientôt.',
'contact.stayInTouch': 'Restons en contact',
'contact.intro': 'Je suis toujours intéressé par de nouveaux projets, des collaborations ou simplement des discussions autour de la technologie. N\'hésitez pas à me contacter !',
'contact.findMeOn': 'Retrouvez-moi aussi sur :',
'contact.sendMessage': 'Envoyez-moi un message',
},
en: {
// Navigation
'nav.home': 'Home',
'nav.about': 'About',
'nav.skills': 'Skills',
'nav.projects': 'Projects',
'nav.education': 'Education',
'nav.contact': 'Contact',
// Boutons
'btn.changeTheme': 'Change theme',
'btn.menu': 'Menu',
'btn.changeLang': 'Français',
'btn.downloadCV': 'Download CV',
'btn.viewProjects': 'View my projects',
'btn.contactMe': 'Contact me',
// Hero
'hero.greeting': 'Hi, I am',
'hero.title': 'Dayron Van Leemput',
'hero.subtitle': 'Computer Technology Student',
'hero.description': 'Bachelor 3 at HELHa Tournai | Young developer passionate about new technologies and innovative application development',
// About
'about.title': 'About me',
'about.subtitle': 'Discover my journey and passions',
'about.stats.year': '3rd',
'about.stats.yearLabel': 'Year of studies',
'about.stats.passion': '100%',
'about.stats.passionLabel': 'Passion',
'about.stats.goals': '∞',
'about.stats.goalsLabel': 'Goals',
'about.stats.fuel': '☕',
'about.stats.fuelLabel': 'Daily fuel',
'about.journey.title': 'My journey',
'about.journey.content': 'Currently in my 3rd year of Computer Technology at HELHa Tournai, I am passionate about application development and new technologies. My journey has allowed me to acquire a solid technical foundation and a methodical approach to development.',
'about.passion.title': 'My passion',
'about.passion.content': 'What drives me most is creating innovative solutions that solve real problems. I particularly enjoy mobile development with Flutter and modern web development with React and TypeScript.',
'about.goals.title': 'My goals',
'about.goals.content': 'I constantly seek to improve my skills and stay up to date with the latest technological trends. My goal is to become a versatile full-stack developer and contribute to projects that have a positive impact.',
'about.quote.title': 'In a few words',
'about.quote.content': 'Technology is nothing. What\'s important is that you have a faith in people, that they\'re basically good and smart, and if you give them tools, they\'ll do wonderful things with them.',
'about.quote.author': 'Steve Jobs',
// Skills
'skills.title': 'My Skills',
'skills.subtitle': 'Technologies and tools I master',
'skills.category.mobile': 'Mobile',
'skills.category.frontend': 'Frontend',
'skills.category.backend': 'Backend',
'skills.category.tools': 'Tools & Others',
'skills.otherSkills': 'Other skills',
'skills.problemSolving': 'Problem solving',
'skills.teamwork': 'Teamwork',
'skills.continuousLearning': 'Continuous learning',
'skills.communication': 'Communication',
// Projects
'projects.title': 'My Projects',
'projects.subtitle': 'Discover my achievements and experiences',
'projects.status.available': 'Coming soon on App Store and Play Store',
'projects.status.current': 'Current project',
'projects.status.online': 'Online',
'projects.features': 'Main features:',
'projects.btn.code': 'Code',
'projects.btn.viewProject': 'View project',
'projects.travelMate.title': 'Travel Mate',
'projects.travelMate.description': 'Mobile application designed to simplify group travel organization. It allows centralizing all important travel information: planning, expense management, activity discovery and coordination between participants.',
'projects.travelMate.feature1': 'Collaborative travel planning',
'projects.travelMate.feature2': 'Shared expense management',
'projects.travelMate.feature3': 'Local activity discovery',
'projects.travelMate.feature4': 'Real-time coordination',
'projects.portfolio.title': 'Web Portfolio',
'projects.portfolio.description': 'Modern and responsive personal website developed with React and TypeScript. Includes fluid animations, dark/light mode and modular architecture.',
'projects.portfolio.feature1': 'Responsive design',
'projects.portfolio.feature2': 'Fluid animations',
'projects.portfolio.feature3': 'Dark/light mode',
'projects.portfolio.feature4': 'Optimized performance',
'projects.shelbys.title': 'Shelbys Bar',
'projects.shelbys.description': 'Elegant showcase website for Shelbys Bar. Presents the atmosphere, menu, and events of the bar with a modern and immersive design.',
'projects.shelbys.feature1': 'Modern and immersive design',
'projects.shelbys.feature2': 'Menu presentation',
'projects.shelbys.feature4': 'Practical information',
// Education
'education.title': 'Education',
'education.subtitle': 'My academic and professional journey',
'education.learningGoals2025': '2025 Learning Goals',
'education.goal1': 'Master Firebase and cloud services',
'education.goal2': 'Deepen Spring Boot for backend',
'education.goal3': 'Learn Docker and containers',
'education.goal4': 'Develop my UI/UX skills',
'education.degree': 'Bachelor in Computer Technology',
'education.school': 'HELHa - Haute École Louvain en Hainaut',
'education.location': 'Tournai, Belgium',
'education.period': '2023 - 2026',
'education.currentYear': '3rd year',
'education.status': 'In progress',
'education.description': 'Complete training in software development, programming, databases, networks and IT project management.',
'education.highlights.0': 'Object-oriented programming (Java, C#)',
'education.highlights.1': 'Web development (HTML, CSS, JavaScript, React)',
'education.highlights.2': 'Mobile development (Flutter, Dart)',
'education.highlights.3': 'Databases and SQL',
'education.highlights.4': 'Project management',
'education.highlights.5': 'Networks and systems',
'education.certifications.title': 'Certifications & Training',
'education.cert1.title': 'Flutter Mobile Development',
'education.cert1.provider': 'Self-taught training',
'education.cert1.date': '2024',
'education.cert2.title': 'React & TypeScript',
'education.cert2.provider': 'Personal projects',
'education.cert2.date': '2024',
'education.highschool.degree': 'CESS (Certificate of Upper Secondary Education)',
'education.highschool.school': 'Athénée Royal d\'Ath',
'education.highschool.location': 'Ath, Belgium',
'education.highschool.period': '2016 - 2022',
'education.highschool.status': 'Graduated',
'education.highschool.description': 'Math-Science Section (Math 8h - Science 7h)',
'education.highschool.highlights.0': 'Advanced Mathematics',
'education.highschool.highlights.1': 'Sciences (Physics, Chemistry, Biology)',
'education.highschool.highlights.2': 'Languages',
// Contact
'contact.title': 'Contact me',
'contact.subtitle': 'A question, a project or just want to chat? Feel free to contact me!',
'contact.form.name': 'Full name',
'contact.form.email': 'Email',
'contact.form.subject': 'Subject',
'contact.form.message': 'Message',
'contact.form.send': 'Send message',
'contact.form.sending': 'Sending...',
'contact.success': 'Message sent successfully! I will reply soon.',
'contact.stayInTouch': 'Let\'s stay in touch',
'contact.intro': 'I am always interested in new projects, collaborations or simply discussions about technology. Feel free to contact me!',
'contact.findMeOn': 'Find me also on:',
'contact.sendMessage': 'Send me a message',
}
};
// Création du contexte
const LanguageContext = createContext<LanguageContextType | undefined>(undefined);
// Provider
interface LanguageProviderProps {
children: ReactNode;
}
export const LanguageProvider: React.FC<LanguageProviderProps> = ({ children }) => {
const [language, setLanguage] = useState<Language>('fr');
// Fonction de traduction
const t = (key: string): string => {
return translations[language][key as keyof typeof translations['fr']] || key;
};
return (
<LanguageContext.Provider value={{ language, setLanguage, t }}>
{children}
</LanguageContext.Provider>
);
};
// Hook personnalisé
export const useLanguage = (): LanguageContextType => {
const context = useContext(LanguageContext);
if (!context) {
throw new Error('useLanguage must be used within a LanguageProvider');
}
return context;
};

View File

@@ -0,0 +1,121 @@
// ========================
// SERVICE D'ENVOI D'EMAIL
// ========================
import emailjs from '@emailjs/browser';
// Configuration EmailJS depuis les variables d'environnement
const EMAILJS_CONFIG = {
SERVICE_ID: import.meta.env.VITE_EMAILJS_SERVICE_ID || 'YOUR_SERVICE_ID',
TEMPLATE_ID: import.meta.env.VITE_EMAILJS_TEMPLATE_ID || 'YOUR_TEMPLATE_ID',
PUBLIC_KEY: import.meta.env.VITE_EMAILJS_PUBLIC_KEY || 'YOUR_PUBLIC_KEY'
};
// Interface pour les données du formulaire
export interface ContactFormData {
name: string;
email: string;
subject: string;
message: string;
}
// Interface pour la réponse
export interface EmailResponse {
success: boolean;
message: string;
}
// Fonction pour envoyer l'email
export const sendContactEmail = async (formData: ContactFormData): Promise<EmailResponse> => {
try {
// Validation des données
if (!formData.name || !formData.email || !formData.subject || !formData.message) {
return {
success: false,
message: 'Tous les champs sont requis.'
};
}
// Validation de l'email
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(formData.email)) {
return {
success: false,
message: 'Veuillez entrer une adresse email valide.'
};
}
// Vérification de la configuration EmailJS
if (!validateEmailJSConfig()) {
console.warn('Configuration EmailJS non trouvée, utilisation du fallback mailto');
// Fallback vers mailto
const mailtoLink = createMailtoLink(formData);
window.location.href = mailtoLink;
return {
success: true,
message: 'Ouverture de votre client email par défaut...'
};
}
console.log('Envoi avec EmailJS...');
console.log('Service ID:', EMAILJS_CONFIG.SERVICE_ID);
console.log('Template ID:', EMAILJS_CONFIG.TEMPLATE_ID);
// Préparation des paramètres pour EmailJS
const templateParams = {
from_name: formData.name,
from_email: formData.email,
subject: formData.subject,
message: formData.message,
to_name: 'Dayron Van Leemput', // Votre nom
reply_to: formData.email
};
// Envoi avec EmailJS
const response = await emailjs.send(
EMAILJS_CONFIG.SERVICE_ID,
EMAILJS_CONFIG.TEMPLATE_ID,
templateParams,
EMAILJS_CONFIG.PUBLIC_KEY
);
if (response.status === 200) {
return {
success: true,
message: 'Message envoyé avec succès ! Je vous répondrai bientôt.'
};
} else {
throw new Error('Erreur lors de l\'envoi');
}
} catch (error) {
console.error('Erreur lors de l\'envoi de l\'email:', error);
return {
success: false,
message: 'Une erreur s\'est produite lors de l\'envoi. Veuillez réessayer ou me contacter directement.'
};
}
};
// Fonction alternative pour fallback (formulaire mailto)
export const createMailtoLink = (formData: ContactFormData): string => {
const subject = encodeURIComponent(`[Portfolio] ${formData.subject}`);
const body = encodeURIComponent(
`Bonjour Dayron,\n\n` +
`${formData.message}\n\n` +
`Cordialement,\n${formData.name}\n` +
`Email: ${formData.email}`
);
return `mailto:dayron.vanleemput@example.com?subject=${subject}&body=${body}`;
};
// Fonction pour valider les credentials EmailJS
export const validateEmailJSConfig = (): boolean => {
return (
EMAILJS_CONFIG.SERVICE_ID !== 'YOUR_SERVICE_ID' &&
EMAILJS_CONFIG.TEMPLATE_ID !== 'YOUR_TEMPLATE_ID' &&
EMAILJS_CONFIG.PUBLIC_KEY !== 'YOUR_PUBLIC_KEY'
);
};

View File

@@ -138,6 +138,19 @@
font-weight: 500; font-weight: 500;
} }
.error-message {
display: flex;
align-items: center;
gap: 12px;
background: #ff4757;
color: white;
padding: 16px;
border-radius: $border-radius;
margin-bottom: 24px;
font-weight: 500;
border: 1px solid #ff3838;
}
.contact-form { .contact-form {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@@ -93,7 +93,8 @@
} }
.theme-toggle, .theme-toggle,
.menu-toggle { .menu-toggle,
.language-toggle {
background: none; background: none;
border: none; border: none;
color: var(--text-secondary); color: var(--text-secondary);
@@ -113,6 +114,19 @@
} }
} }
.language-toggle {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 10px;
.language-text {
font-size: map-get($font-sizes, sm);
font-weight: 600;
line-height: 1;
}
}
.menu-toggle { .menu-toggle {
display: none; display: none;

View File

@@ -37,7 +37,7 @@
&-title { &-title {
font-size: map-get($font-sizes, 4xl); font-size: map-get($font-sizes, 4xl);
font-weight: 800; font-weight: 800;
margin-bottom: 16px; margin: 20px;
@include gradient-text(); @include gradient-text();
@include respond-to(sm) { @include respond-to(sm) {
@@ -99,6 +99,10 @@
display: flex; display: flex;
gap: 16px; gap: 16px;
@include respond-to(md) {
justify-content: center;
}
.social-link { .social-link {
@include flex-center(); @include flex-center();
width: 48px; width: 48px;
@@ -131,27 +135,56 @@
} }
} }
.avatar-placeholder { .avatar-image {
width: 300px; width: 300px;
height: 300px; height: 300px;
border-radius: 50%; border-radius: 50%;
background: linear-gradient(135deg, $primary-color, $accent-color); object-fit: cover;
@include flex-center(); object-position: center;
font-size: map-get($font-sizes, 4xl);
font-weight: 800;
color: white;
@include box-shadow(large); @include box-shadow(large);
border: 4px solid white;
// Ajouter un effet de dégradé subtil en overlay
position: relative;
&::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 50%;
background: linear-gradient(135deg,
rgba($primary-color, 0.1) 0%,
rgba($accent-color, 0.1) 100%);
pointer-events: none;
}
@include respond-to(md) { @include respond-to(md) {
width: 250px; width: 250px;
height: 250px; height: 250px;
font-size: map-get($font-sizes, 3xl); border-width: 3px;
} }
@include respond-to(sm) { @include respond-to(sm) {
width: 200px; width: 200px;
height: 200px; height: 200px;
font-size: map-get($font-sizes, 2xl); border-width: 2px;
}
// Effet de survol amélioré pour les images
@media (hover: hover) {
&:hover {
transform: scale(1.02);
@include box-shadow(large);
&::after {
background: linear-gradient(135deg,
rgba($primary-color, 0.2) 0%,
rgba($accent-color, 0.2) 100%);
}
}
} }
} }