first commit
This commit is contained in:
341
src/components/Contact.tsx
Normal file
341
src/components/Contact.tsx
Normal file
@@ -0,0 +1,341 @@
|
||||
import { useState } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Mail, Phone, MapPin, Send, Github, Linkedin, MessageCircle, CheckCircle } from 'lucide-react';
|
||||
|
||||
const Contact = () => {
|
||||
const [formData, setFormData] = useState({
|
||||
name: '',
|
||||
email: '',
|
||||
subject: '',
|
||||
message: ''
|
||||
});
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [isSubmitted, setIsSubmitted] = useState(false);
|
||||
|
||||
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);
|
||||
|
||||
// Simulation d'envoi (remplacez par votre logique d'envoi réelle)
|
||||
setTimeout(() => {
|
||||
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 = [
|
||||
{
|
||||
icon: <Mail size={24} />,
|
||||
title: "Email",
|
||||
content: "dayron.vanleemput@example.com", // Remplacez par votre email
|
||||
link: "mailto:dayron.vanleemput@example.com",
|
||||
color: "#EA4335"
|
||||
},
|
||||
{
|
||||
icon: <Phone size={24} />,
|
||||
title: "Téléphone",
|
||||
content: "+32 XXX XX XX XX", // Remplacez par votre numéro
|
||||
link: "tel:+32XXXXXXXXX",
|
||||
color: "#34A853"
|
||||
},
|
||||
{
|
||||
icon: <MapPin size={24} />,
|
||||
title: "Localisation",
|
||||
content: "Tournai, Belgique",
|
||||
link: "https://maps.google.com/?q=Tournai,Belgium",
|
||||
color: "#4285F4"
|
||||
}
|
||||
];
|
||||
|
||||
const socialLinks = [
|
||||
{
|
||||
icon: <Github size={24} />,
|
||||
name: "GitHub",
|
||||
url: "https://github.com/dayronvanleemput", // Remplacez par votre profil
|
||||
color: "#333"
|
||||
},
|
||||
{
|
||||
icon: <Linkedin size={24} />,
|
||||
name: "LinkedIn",
|
||||
url: "https://linkedin.com/in/dayronvanleemput", // 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">Contactez-moi</h2>
|
||||
<p className="section-subtitle">
|
||||
Une question, un projet ou simplement envie d'échanger ? N'hésitez pas à me contacter !
|
||||
</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} />
|
||||
Restons en contact
|
||||
</h3>
|
||||
<p>
|
||||
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 !
|
||||
</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>Retrouvez-moi aussi sur :</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>Envoyez-moi un message</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} />
|
||||
Message envoyé avec succès ! Je vous répondrai bientôt.
|
||||
</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">Nom complet</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
name="name"
|
||||
value={formData.name}
|
||||
onChange={handleChange}
|
||||
required
|
||||
placeholder="Votre nom"
|
||||
/>
|
||||
</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">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">Sujet</label>
|
||||
<input
|
||||
type="text"
|
||||
id="subject"
|
||||
name="subject"
|
||||
value={formData.subject}
|
||||
onChange={handleChange}
|
||||
required
|
||||
placeholder="Objet de votre message"
|
||||
/>
|
||||
</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">Message</label>
|
||||
<textarea
|
||||
id="message"
|
||||
name="message"
|
||||
value={formData.message}
|
||||
onChange={handleChange}
|
||||
required
|
||||
rows={6}
|
||||
placeholder="Votre 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" />
|
||||
Envoi en cours...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Send size={20} />
|
||||
Envoyer le message
|
||||
</>
|
||||
)}
|
||||
</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. Développé avec ❤️ en React et TypeScript.
|
||||
</p>
|
||||
</motion.footer>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Contact;
|
||||
Reference in New Issue
Block a user