Files
xeewy.be/src/components/Contact.tsx
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

375 lines
12 KiB
TypeScript

import { useState } from 'react';
import { motion } from 'framer-motion';
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 { t } = useLanguage();
const [formData, setFormData] = useState({
name: '',
email: '',
subject: '',
message: ''
});
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSubmitted, setIsSubmitted] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setIsSubmitting(true);
setError(null);
try {
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);
}
};
const contactInfo = [
{
icon: <Mail size={24} />,
title: "Email",
content: "dayronvanleemput@gmail.com",
link: "mailto:dayronvanleemput@gmail.com",
color: "#EA4335"
},
{
icon: <Phone size={24} />,
title: "Téléphone",
content: "+32 455 19 47 62",
link: "tel:+32455194762",
color: "#34A853"
},
{
icon: <MapPin size={24} />,
title: "Localisation",
content: "Ath, Belgique",
link: "https://maps.google.com/?q=Ath,Belgium",
color: "#4285F4"
}
];
const socialLinks = [
{
icon: <Github size={24} />,
name: "GitHub",
url: "https://github.com/Dayron-HELHa", // Remplacez par votre profil
color: "#333"
},
{
icon: <Linkedin size={24} />,
name: "LinkedIn",
url: "https://www.linkedin.com/in/dayron-van-leemput-992a94398", // Remplacez par votre profil
color: "#0077B5"
}
];
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.2,
delayChildren: 0.3
}
}
};
const itemVariants = {
hidden: { opacity: 0, y: 50 },
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.6,
ease: [0.25, 0.1, 0.25, 1] as const
}
}
};
return (
<section id="contact" className="contact">
<div className="container">
<motion.div
className="section-header"
initial={{ opacity: 0, y: 50 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
viewport={{ once: true }}
>
<h2 className="section-title">{t('contact.title')}</h2>
<p className="section-subtitle">
{t('contact.subtitle')}
</p>
</motion.div>
<motion.div
className="contact-content"
variants={containerVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
>
{/* Informations de contact */}
<motion.div className="contact-info" variants={itemVariants}>
<div className="contact-intro">
<h3>
<MessageCircle size={24} />
{t('contact.stayInTouch')}
</h3>
<p>
{t('contact.intro')}
</p>
</div>
<div className="contact-methods">
{contactInfo.map((contact, index) => (
<motion.a
key={index}
href={contact.link}
className="contact-method"
initial={{ opacity: 0, x: -30 }}
whileInView={{ opacity: 1, x: 0 }}
transition={{ duration: 0.5, delay: index * 0.1 }}
whileHover={{
scale: 1.05,
x: 10,
transition: { duration: 0.2 }
}}
viewport={{ once: true }}
>
<div
className="contact-icon"
style={{ backgroundColor: `${contact.color}20`, color: contact.color }}
>
{contact.icon}
</div>
<div className="contact-details">
<h4>{contact.title}</h4>
<span>{contact.content}</span>
</div>
</motion.a>
))}
</div>
<div className="social-links">
<h4>{t('contact.findMeOn')}</h4>
<div className="social-grid">
{socialLinks.map((social, index) => (
<motion.a
key={index}
href={social.url}
target="_blank"
rel="noopener noreferrer"
className="social-link"
initial={{ opacity: 0, scale: 0.8 }}
whileInView={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.5, delay: index * 0.1 }}
whileHover={{
scale: 1.1,
rotate: 5,
transition: { duration: 0.2 }
}}
viewport={{ once: true }}
>
<div
className="social-icon"
style={{ backgroundColor: `${social.color}20`, color: social.color }}
>
{social.icon}
</div>
<span>{social.name}</span>
</motion.a>
))}
</div>
</div>
</motion.div>
{/* Formulaire de contact */}
<motion.div className="contact-form-container" variants={itemVariants}>
<h3>{t('contact.sendMessage')}</h3>
{isSubmitted && (
<motion.div
className="success-message"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.8 }}
>
<CheckCircle size={20} />
{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>
)}
<form onSubmit={handleSubmit} className="contact-form">
<div className="form-row">
<motion.div
className="form-group"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.1 }}
viewport={{ once: true }}
>
<label htmlFor="name">{t('contact.form.name')}</label>
<input
type="text"
id="name"
name="name"
value={formData.name}
onChange={handleChange}
required
placeholder={t('contact.form.name')}
/>
</motion.div>
<motion.div
className="form-group"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.2 }}
viewport={{ once: true }}
>
<label htmlFor="email">{t('contact.form.email')}</label>
<input
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleChange}
required
placeholder="votre.email@example.com"
/>
</motion.div>
</div>
<motion.div
className="form-group"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.3 }}
viewport={{ once: true }}
>
<label htmlFor="subject">{t('contact.form.subject')}</label>
<input
type="text"
id="subject"
name="subject"
value={formData.subject}
onChange={handleChange}
required
placeholder={t('contact.form.subject')}
/>
</motion.div>
<motion.div
className="form-group"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.4 }}
viewport={{ once: true }}
>
<label htmlFor="message">{t('contact.form.message')}</label>
<textarea
id="message"
name="message"
value={formData.message}
onChange={handleChange}
required
rows={6}
placeholder={t('contact.form.message')}
/>
</motion.div>
<motion.button
type="submit"
className={`submit-btn ${isSubmitting ? 'submitting' : ''}`}
disabled={isSubmitting}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.5 }}
whileHover={!isSubmitting ? { scale: 1.05 } : {}}
whileTap={!isSubmitting ? { scale: 0.95 } : {}}
viewport={{ once: true }}
>
{isSubmitting ? (
<>
<div className="loading-spinner" />
{t('contact.form.sending')}
</>
) : (
<>
<Send size={20} />
{t('contact.form.send')}
</>
)}
</motion.button>
</form>
</motion.div>
</motion.div>
{/* Footer */}
<motion.footer
className="contact-footer"
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
transition={{ duration: 0.8, delay: 0.5 }}
viewport={{ once: true }}
>
<p>
© 2025 Dayron Van Leemput.
</p>
</motion.footer>
</div>
</section>
);
};
export default Contact;