212 lines
No EOL
8.1 KiB
TypeScript
212 lines
No EOL
8.1 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { Link } from 'react-router-dom';
|
|
|
|
const Footer: React.FC = () => {
|
|
const [formData, setFormData] = useState({
|
|
name: '',
|
|
email: '',
|
|
subject: '',
|
|
message: '',
|
|
honeypot: '',
|
|
});
|
|
const [status, setStatus] = useState('');
|
|
const [errors, setErrors] = useState<Partial<typeof formData>>({});
|
|
const [lastSubmissionTime, setLastSubmissionTime] = useState(0);
|
|
|
|
useEffect(() => {
|
|
const storedTime = localStorage.getItem('lastSubmissionTime');
|
|
if (storedTime) {
|
|
setLastSubmissionTime(parseInt(storedTime, 10));
|
|
}
|
|
}, []);
|
|
|
|
const validateForm = (data: typeof formData) => {
|
|
const errors: Partial<typeof formData> = {};
|
|
if (!data.name.trim()) errors.name = "Name is required";
|
|
if (!data.email.trim()) errors.email = "Email is required";
|
|
else if (!/\S+@\S+\.\S+/.test(data.email)) errors.email = "Email is invalid";
|
|
if (!data.subject.trim()) errors.subject = "Subject is required";
|
|
if (!data.message.trim()) errors.message = "Message is required";
|
|
return errors;
|
|
};
|
|
|
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
|
const { name, value } = e.target;
|
|
setFormData({ ...formData, [name]: value });
|
|
setErrors({ ...errors, [name]: '' });
|
|
};
|
|
|
|
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
|
e.preventDefault();
|
|
const validationErrors = validateForm(formData);
|
|
if (Object.keys(validationErrors).length > 0) {
|
|
setErrors(validationErrors);
|
|
setStatus('Please correct the errors in the form.');
|
|
return;
|
|
}
|
|
|
|
if (formData.honeypot) {
|
|
setStatus('Form submission rejected.');
|
|
return;
|
|
}
|
|
|
|
const currentTime = Date.now();
|
|
if (currentTime - lastSubmissionTime < 60000) {
|
|
setStatus('Please wait a moment before submitting again.');
|
|
return;
|
|
}
|
|
|
|
setStatus('Sending...');
|
|
|
|
try {
|
|
const response = await fetch('/api/sendEmail', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify(formData),
|
|
});
|
|
|
|
const contentType = response.headers.get("content-type");
|
|
if (contentType && contentType.indexOf("application/json") !== -1) {
|
|
const data = await response.json();
|
|
if (response.ok) {
|
|
setStatus('Message sent successfully!');
|
|
setFormData({ name: '', email: '', subject: '', message: '', honeypot: '' });
|
|
setLastSubmissionTime(currentTime);
|
|
localStorage.setItem('lastSubmissionTime', currentTime.toString());
|
|
} else {
|
|
setStatus(`Failed to send message: ${data.error || 'Unknown error'}`);
|
|
}
|
|
} else {
|
|
setStatus('Received non-JSON response from server');
|
|
}
|
|
} catch (error: unknown) {
|
|
console.error('Error sending message:', error);
|
|
setStatus(`An error occurred: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<footer className="bg-[#8C1D40] text-white py-8">
|
|
<div className="container mx-auto px-4">
|
|
<div className="flex flex-wrap justify-between">
|
|
<div className="w-full md:w-1/3 mb-6 md:mb-0">
|
|
<h3 className="text-xl font-bold mb-4">MCDi</h3>
|
|
<p className="text-sm">Missoula Council of the Deaf, Inc.</p>
|
|
<p className="text-sm mt-2">© MCDi {new Date().getFullYear()}</p>
|
|
<a
|
|
href="https://www.facebook.com/profile.php?id=61564416121195"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="inline-block mt-4 transition-transform duration-300 ease-in-out transform hover:scale-110"
|
|
>
|
|
<img
|
|
src="logos/facebook-logo.png"
|
|
alt="Facebook"
|
|
className="w-10 h-10 rounded-full shadow-lg hover:shadow-xl"
|
|
style={{
|
|
filter: 'drop-shadow(0px 4px 6px rgba(0, 0, 0, 0.1))',
|
|
}}
|
|
/>
|
|
</a>
|
|
</div>
|
|
<div className="w-full md:w-1/3 mb-6 md:mb-0">
|
|
<h3 className="text-xl font-bold mb-4">Quick Links</h3>
|
|
<ul className="text-sm grid grid-cols-2 md:grid-cols-1 gap-2">
|
|
<li><Link to="/" className="hover:text-yellow-200 transition-colors duration-200">Home</Link></li>
|
|
<li><Link to="/calendar" className="hover:text-yellow-200 transition-colors duration-200">Calendar</Link></li>
|
|
<li><Link to="/bylaws" className="hover:text-yellow-200 transition-colors duration-200">Bylaws</Link></li>
|
|
<li><Link to="/sponsors" className="hover:text-yellow-200 transition-colors duration-200">Sponsors</Link></li>
|
|
<li><Link to="/meet-board" className="hover:text-yellow-200 transition-colors duration-200">Meet MCDi Board</Link></li>
|
|
</ul>
|
|
</div>
|
|
<div className="w-full md:w-1/3">
|
|
<h3 className="text-xl font-bold mb-4">Contact Us</h3>
|
|
<form className="text-sm" onSubmit={handleSubmit}>
|
|
<div className="mb-2">
|
|
<input
|
|
type="text"
|
|
name="name"
|
|
value={formData.name}
|
|
onChange={handleChange}
|
|
placeholder="Name"
|
|
className={`w-full p-2 text-black rounded ${errors.name ? 'border-red-500' : ''}`}
|
|
required
|
|
/>
|
|
{errors.name && <p className="text-red-500 text-xs mt-1">{errors.name}</p>}
|
|
</div>
|
|
<div className="mb-2">
|
|
<input
|
|
type="email"
|
|
name="email"
|
|
value={formData.email}
|
|
onChange={handleChange}
|
|
placeholder="Your Email Address"
|
|
className={`w-full p-2 text-black rounded ${errors.email ? 'border-red-500' : ''}`}
|
|
required
|
|
/>
|
|
{errors.email && <p className="text-red-500 text-xs mt-1">{errors.email}</p>}
|
|
</div>
|
|
<div className="mb-2">
|
|
<input
|
|
type="text"
|
|
name="subject"
|
|
value={formData.subject}
|
|
onChange={handleChange}
|
|
placeholder="Subject"
|
|
className={`w-full p-2 text-black rounded ${errors.subject ? 'border-red-500' : ''}`}
|
|
required
|
|
/>
|
|
{errors.subject && <p className="text-red-500 text-xs mt-1">{errors.subject}</p>}
|
|
</div>
|
|
<div className="mb-2">
|
|
<textarea
|
|
name="message"
|
|
value={formData.message}
|
|
onChange={handleChange}
|
|
placeholder="Message"
|
|
rows={3}
|
|
className={`w-full p-2 text-black rounded ${errors.message ? 'border-red-500' : ''}`}
|
|
required
|
|
></textarea>
|
|
{errors.message && <p className="text-red-500 text-xs mt-1">{errors.message}</p>}
|
|
</div>
|
|
<input
|
|
type="text"
|
|
name="honeypot"
|
|
value={formData.honeypot}
|
|
onChange={handleChange}
|
|
style={{display: 'none'}}
|
|
tabIndex={-1}
|
|
autoComplete="off"
|
|
/>
|
|
<button
|
|
type="submit"
|
|
className="bg-yellow-400 text-[#8C1D40] font-bold py-2 px-4 rounded hover:bg-yellow-300 transition-colors duration-200"
|
|
>
|
|
Send
|
|
</button>
|
|
{status && <p className="mt-2 text-sm">{status}</p>}
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<div className="w-full text-center mt-8 text-sm">
|
|
<p>
|
|
Designed by{' '}
|
|
<a
|
|
href="https://www.deafgain.org"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="text-yellow-200 hover:text-yellow-400 transition-colors duration-200"
|
|
>
|
|
DeafGain, LLC
|
|
</a>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
);
|
|
};
|
|
|
|
export default Footer; |