first commit

This commit is contained in:
2025-08-14 00:31:16 -04:00
commit 2208c6bdfd
37 changed files with 4049 additions and 0 deletions

24
.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

8
Makefile Normal file
View File

@ -0,0 +1,8 @@
dev-local:
npm run dev
dev-remote:
npm run dev -- --host
build:
npm run build

3
api/go.mod Normal file
View File

@ -0,0 +1,3 @@
module npfd-api
go 1.24

105
api/main.go Normal file
View File

@ -0,0 +1,105 @@
package main
import (
"fmt"
"log"
"net/http"
"net/smtp"
)
func contactFormHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// Parse form data
if err := r.ParseForm(); err != nil {
http.Error(w, "Error parsing form", http.StatusBadRequest)
return
}
name := r.FormValue("name")
email := r.FormValue("email")
message := r.FormValue("message")
// Basic validation
if name == "" || email == "" || message == "" {
http.Error(w, "All fields are required", http.StatusBadRequest)
return
}
// In a real application, you'd send an email or save to a database here
// For demonstration, print to console
fmt.Printf("Received contact form submission:\nName: %s\nEmail: %s\nMessage: %s\n", name, email, message)
// SMTP server details for Stalwart
smtpHost := "your_stalwart_hostname.com" // Replace with your Stalwart server hostname
smtpPort := "587" // Standard submission port with STARTTLS
smtpAddress := smtpHost + ":" + smtpPort
// Authentication credentials
username := "your_email@your_domain.com" // Replace with a valid Stalwart email account
password := "your_email_password" // Replace with the password for the email account
// Sender and recipient details
from := username
to := []string{"recipient@example.com"} // Replace with the recipient's email address
// Email message
msg := []byte("To: recipient@example.com\r\n" +
"Subject: Test Email from Go and Stalwart\r\n" +
"\r\n" +
"This is a test email sent from a Go application through Stalwart.")
// Set up authentication
auth := smtp.PlainAuth("", username, password, smtpHost)
// Send the email
err := smtp.SendMail(smtpAddress, auth, from, to, msg)
if err != nil {
log.Fatalf("Error sending email: %v", err)
}
log.Println("Email sent successfully!")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Contact form submitted successfully!")
}
func main() {
http.HandleFunc("/v1/api/contact", contactFormHandler)
fmt.Println("Server listening on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
// // SMTP server details for Stalwart
// smtpHost := "mail.npfd.info" // Replace with your Stalwart server hostname
// smtpPort := "587" // Standard submission port with STARTTLS
// smtpAddress := smtpHost + ":" + smtpPort
// // Authentication credentials
// username := "postman@npfd.info" // Replace with a valid Stalwart email account
// password := "TOPSECRET" // Replace with the password for the email account
// // Sender and recipient details
// from := username
// to := []string{"recipient@example.com"} // Replace with the recipient's email address
// // Email message
// msg := []byte("To: recipient@example.com\r\n" +
// "Subject: Test Email from Go and Stalwart\r\n" +
// "\r\n" +
// "This is a test email sent from a Go application through Stalwart.")
// // Set up authentication
// auth := smtp.PlainAuth("", username, password, smtpHost)
// // Send the email
// err := smtp.SendMail(smtpAddress, auth, from, to, msg)
// if err != nil {
// log.Fatalf("Error sending email: %v", err)
// }
// log.Println("Email sent successfully!")
}

12
index.html Normal file
View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>New Providence Fire Department</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

2736
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

23
package.json Normal file
View File

@ -0,0 +1,23 @@
{
"name": "npfd-web",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite --host",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^5.5.0",
"react-router-dom": "^7.8.0"
},
"devDependencies": {
"@vitejs/plugin-react": "^4.0.0",
"autoprefixer": "^10.4.16",
"postcss": "^8.4.31",
"tailwindcss": "^3.3.0",
"vite": "^4.3.9"
}
}

6
postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

16
src/App.jsx Normal file
View File

@ -0,0 +1,16 @@
import React from "react";
import { Outlet } from "react-router-dom";
import Header from "./components/Header";
import Footer from "./components/Footer";
export default function App() {
return (
<div style={{ display: "flex", flexDirection: "column", minHeight: "100vh" }}>
<Header />
<main style={{ flex: 1, padding: "20px" }}>
<Outlet />
</main>
<Footer />
</div>
);
}

35
src/components/Footer.jsx Normal file
View File

@ -0,0 +1,35 @@
import { FaFacebookF, FaInstagram, FaXTwitter } from 'react-icons/fa6';
const Footer = () => {
return (
<footer className="bg-gray-900 text-gray-400">
<div className="max-w-7xl mx-auto py-12 px-4 sm:px-6 lg:px-8">
<div className="md:flex md:items-center md:justify-between">
<div className="flex justify-center space-x-6 md:order-2">
<a href="https://facebook.com/" target="_blank" rel="noopener noreferrer" className="hover:text-white">
<FaFacebookF size={24} />
</a>
<a href="https://instagram.com/" target="_blank" rel="noopener noreferrer" className="hover:text-white">
<FaInstagram size={24} />
</a>
<a href="https://x.com/" target="_blank" rel="noopener noreferrer" className="hover:text-white">
<FaXTwitter size={24} />
</a>
</div>
<div className="mt-8 md:mt-0 md:order-1">
<p className="text-center text-base text-gray-400">
&copy; {new Date().getFullYear()} New Providence Fire Department. All rights reserved.
</p>
<p className="text-center text-xs text-gray-500 mt-2">
This site is protected by reCAPTCHA and the Google
<a href="https://policies.google.com/privacy" className="text-gray-400 hover:text-white" target="_blank" rel="noopener noreferrer"> Privacy Policy</a> and
<a href="https://policies.google.com/terms" className="text-gray-400 hover:text-white" target="_blank" rel="noopener noreferrer"> Terms of Service</a> apply.
</p>
</div>
</div>
</div>
</footer>
);
};
export default Footer;

105
src/components/Header.jsx Normal file
View File

@ -0,0 +1,105 @@
import React, { useState } from 'react';
import { Link } from "react-router-dom";
const FireIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" className="h-8 w-8 text-red-600" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M10 2a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 2zM5.505 5.505a.75.75 0 011.06 0l1.061 1.06a.75.75 0 01-1.06 1.06l-1.06-1.06a.75.75 0 010-1.06zM14.495 5.505a.75.75 0 010 1.06l-1.06 1.06a.75.75 0 01-1.06-1.06l1.06-1.06a.75.75 0 011.06 0zM10 4.5a.75.75 0 01.75.75v5.5a.75.75 0 01-1.5 0V5.25A.75.75 0 0110 4.5zM7.163 15.163a.75.75 0 011.06 0l1.061 1.06a.75.75 0 01-1.06 1.06l-1.06-1.06a.75.75 0 010-1.06zM12.837 15.163a.75.75 0 010 1.06l-1.06 1.06a.75.75 0 01-1.06-1.06l1.06-1.06a.75.75 0 011.06 0zM10 18a1 1 0 01-1-1v-2.5a1 1 0 112 0V17a1 1 0 01-1 1z" clipRule="evenodd" />
<path d="M10 18a8 8 0 100-16 8 8 0 000 16zM4.75 5.25a.75.75 0 000 1.5h10.5a.75.75 0 000-1.5H4.75z" />
<path d="M10 18a8 8 0 100-16 8 8 0 000 16zM4.75 5.25a.75.75 0 000 1.5h10.5a.75.75 0 000-1.5H4.75z" />
<path d="M12.96 5.04a5.5 5.5 0 01-5.92 0 1 1 0 00-1.08 1.68 7.5 7.5 0 008.08 0 1 1 0 00-1.08-1.68z" />
</svg>
);
const Header = () => {
const [isOpen, setIsOpen] = useState(false);
const navLinks = [
{ name: 'Home', href: '/' },
{ name: 'Our Team', href: '/team/' },
{ name: 'Apparatus', href: '/apparatus/' },
{ name: 'Join Us', href: '/join/' },
{ name: 'Contact', href: '/contact/' },
];
return (
<header className="bg-gray-900 text-white shadow-md sticky top-0 z-50">
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-20">
<a href="/" className="flex items-center space-x-2">
<img src="https://newprovidencefire.org/wp-content/uploads/2020/09/cropped-favico-32x32.png" alt="Logo" className="h-8 w-8"/>
<span className="text-xl font-bold tracking-tight">New Providence Fire Dept.</span>
</a>
<nav className="hidden md:flex items-center space-x-6">
{navLinks.map((link) => (
<Link
key={link.name}
to={link.href}
className="text-gray-300 hover:text-white hover:bg-gray-700 px-3 py-2 rounded-md text-sm font-medium transition-colors duration-200"
>
{link.name}
</Link>
))}
<a
href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=VCMML5H7AWYB2&source=url"
target="_blank"
rel="noopener noreferrer"
className="bg-red-600 hover:bg-red-700 text-white font-bold py-2 px-4 rounded-lg transition-all duration-300 transform hover:scale-105"
>
Donate
</a>
{/* {navLinks.map((link) => (
<a key={link.name} href={link.path} className="text-gray-300 hover:text-white hover:bg-gray-700 px-3 py-2 rounded-md text-sm font-medium transition-colors duration-200">
{link.name}
</a>
))} */}
</nav>
<div className="md:hidden flex items-center">
<button onClick={() => setIsOpen(!isOpen)} className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white">
<span className="sr-only">Open main menu</span>
{isOpen ? (
<svg className="block h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
</svg>
) : (
<svg className="block h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
)}
</button>
</div>
</div>
</div>
{isOpen && (
<div className="md:hidden">
<div className="px-2 pt-2 pb-3 space-y-1 sm:px-3">
{navLinks.map((link) => (
<a key={link.name} href={link.href} className="text-gray-300 hover:text-white hover:bg-gray-700 block px-3 py-2 rounded-md text-base font-medium transition-colors duration-200">
{link.name}
</a>
))}
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=VCMML5H7AWYB2&source=url" target="_blank" rel="noopener noreferrer" className="bg-red-600 hover:bg-red-700 text-white block w-full text-center mt-2 font-bold py-2 px-4 rounded-lg transition-all duration-300 transform hover:scale-105">
Donate
</a>
</div>
</div>
)}
</header>
);
};
export default Header;
{/* {navLinks.map((link) => (
<Link
key={link.path}
to={link.path}
style={{
color: location.pathname === link.path ? "yellow" : "white",
fontWeight: location.pathname === link.path ? "bold" : "normal",
textDecoration: "none",
}}
>
{link.title}
</Link>
))} */}

View File

@ -0,0 +1,46 @@
const CheckIcon = () => (
<svg className="h-6 w-6 text-green-500 flex-shrink-0 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7" />
</svg>
);
const ApparatusCar1 = () => {
const prerequisites = [
"16-18 years old; enrolled in high school and be at least a rising junior",
"Must be in good physical condition",
"Must have permission from a parent or guardian",
"Must be in good standing in school, community and have no problems with the law",
];
return (
<section className="bg-gray-100 py-20">
<div id="junior-member" className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid md:grid-cols-2 gap-12 items-center">
<div className="text-center flex flex-col items-center justify-center bg-white p-8 rounded-lg shadow-inner">
<span className="text-7xl md:text-9xl font-extrabold text-red-600">16</span>
<span className="text-2xl md:text-3xl font-bold text-gray-800 mt-2">years old to apply</span>
</div>
<div>
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-6">Car 1</h2>
<p className="text-gray-600 mb-4">
The mission of our Junior program is to offer our high-school students an opportunity to serve in their community. Not required, but our goal is to have Juniors become Full Members after high school graduation.
</p>
<p className="text-gray-600 mb-6">
The Prerequisites are similar to Full Members, with the following differences:
</p>
<ul className="space-y-4">
{prerequisites.map((item, index) => (
<li key={index} className="flex items-start">
<CheckIcon />
<span className="text-gray-700">{item}</span>
</li>
))}
</ul>
</div>
</div>
</div>
</section>
);
};
export default ApparatusCar1;

View File

@ -0,0 +1,46 @@
const CheckIcon = () => (
<svg className="h-6 w-6 text-green-500 flex-shrink-0 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7" />
</svg>
);
const ApparatusEngine1 = () => {
const prerequisites = [
"Candidate must be between 18 and 50 years of age, prior it his or her 51st birthday",
"Have a valid NJ State Driver License",
"Commit to Wednesday Night Training from 7pm-9pm",
"Interviewed by Chief and or President",
"Good moral character",
"Apparent adequate physical strength and stamina",
"Live or work in New Providence or within the area",
"Physical Well Being Certificate Exam by a physician",
"Complete background check including a fingerprint process with the New Providence Police Dept.",
];
return (
<section className="bg-white py-20">
<div id="full-member" className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid md:grid-cols-2 gap-12 items-center">
<div className="order-2 md:order-1">
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-6">Engine 1</h2>
<p className="text-gray-600 mb-6">The broad prerequisites for full membership are:</p>
<ul className="space-y-4">
{prerequisites.map((item, index) => (
<li key={index} className="flex items-start">
<CheckIcon />
<span className="text-gray-700">{item}</span>
</li>
))}
</ul>
</div>
<div className="order-1 md:order-2 text-center flex flex-col items-center justify-center bg-gray-100 p-8 rounded-lg shadow-inner">
<span className="text-7xl md:text-9xl font-extrabold text-red-600">18</span>
<span className="text-2xl md:text-3xl font-bold text-gray-800 mt-2">years old to apply</span>
</div>
</div>
</div>
</section>
);
};
export default ApparatusEngine1;

View File

@ -0,0 +1,46 @@
const CheckIcon = () => (
<svg className="h-6 w-6 text-green-500 flex-shrink-0 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7" />
</svg>
);
const ApparatusEngine2 = () => {
const prerequisites = [
"16-18 years old; enrolled in high school and be at least a rising junior",
"Must be in good physical condition",
"Must have permission from a parent or guardian",
"Must be in good standing in school, community and have no problems with the law",
];
return (
<section className="bg-gray-100 py-20">
<div id="junior-member" className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid md:grid-cols-2 gap-12 items-center">
<div className="text-center flex flex-col items-center justify-center bg-white p-8 rounded-lg shadow-inner">
<span className="text-7xl md:text-9xl font-extrabold text-red-600">16</span>
<span className="text-2xl md:text-3xl font-bold text-gray-800 mt-2">years old to apply</span>
</div>
<div>
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-6">Engine 2</h2>
<p className="text-gray-600 mb-4">
The mission of our Junior program is to offer our high-school students an opportunity to serve in their community. Not required, but our goal is to have Juniors become Full Members after high school graduation.
</p>
<p className="text-gray-600 mb-6">
The Prerequisites are similar to Full Members, with the following differences:
</p>
<ul className="space-y-4">
{prerequisites.map((item, index) => (
<li key={index} className="flex items-start">
<CheckIcon />
<span className="text-gray-700">{item}</span>
</li>
))}
</ul>
</div>
</div>
</div>
</section>
);
};
export default ApparatusEngine2;

View File

@ -0,0 +1,46 @@
const CheckIcon = () => (
<svg className="h-6 w-6 text-green-500 flex-shrink-0 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7" />
</svg>
);
const ApparatusEngine4 = () => {
const prerequisites = [
"16-18 years old; enrolled in high school and be at least a rising junior",
"Must be in good physical condition",
"Must have permission from a parent or guardian",
"Must be in good standing in school, community and have no problems with the law",
];
return (
<section className="bg-gray-100 py-20">
<div id="junior-member" className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid md:grid-cols-2 gap-12 items-center">
<div className="text-center flex flex-col items-center justify-center bg-white p-8 rounded-lg shadow-inner">
<span className="text-7xl md:text-9xl font-extrabold text-red-600">16</span>
<span className="text-2xl md:text-3xl font-bold text-gray-800 mt-2">years old to apply</span>
</div>
<div>
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-6">Engine 4</h2>
<p className="text-gray-600 mb-4">
The mission of our Junior program is to offer our high-school students an opportunity to serve in their community. Not required, but our goal is to have Juniors become Full Members after high school graduation.
</p>
<p className="text-gray-600 mb-6">
The Prerequisites are similar to Full Members, with the following differences:
</p>
<ul className="space-y-4">
{prerequisites.map((item, index) => (
<li key={index} className="flex items-start">
<CheckIcon />
<span className="text-gray-700">{item}</span>
</li>
))}
</ul>
</div>
</div>
</div>
</section>
);
};
export default ApparatusEngine4;

View File

@ -0,0 +1,46 @@
const CheckIcon = () => (
<svg className="h-6 w-6 text-green-500 flex-shrink-0 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7" />
</svg>
);
const ApparatusRescue8 = () => {
const prerequisites = [
"Candidate must be between 18 and 50 years of age, prior it his or her 51st birthday",
"Have a valid NJ State Driver License",
"Commit to Wednesday Night Training from 7pm-9pm",
"Interviewed by Chief and or President",
"Good moral character",
"Apparent adequate physical strength and stamina",
"Live or work in New Providence or within the area",
"Physical Well Being Certificate Exam by a physician",
"Complete background check including a fingerprint process with the New Providence Police Dept.",
];
return (
<section className="bg-white py-20">
<div id="full-member" className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid md:grid-cols-2 gap-12 items-center">
<div className="order-2 md:order-1">
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-6">Rescue 8</h2>
<p className="text-gray-600 mb-6">The broad prerequisites for full membership are:</p>
<ul className="space-y-4">
{prerequisites.map((item, index) => (
<li key={index} className="flex items-start">
<CheckIcon />
<span className="text-gray-700">{item}</span>
</li>
))}
</ul>
</div>
<div className="order-1 md:order-2 text-center flex flex-col items-center justify-center bg-gray-100 p-8 rounded-lg shadow-inner">
<span className="text-7xl md:text-9xl font-extrabold text-red-600">18</span>
<span className="text-2xl md:text-3xl font-bold text-gray-800 mt-2">years old to apply</span>
</div>
</div>
</div>
</section>
);
};
export default ApparatusRescue8;

View File

@ -0,0 +1,46 @@
const CheckIcon = () => (
<svg className="h-6 w-6 text-green-500 flex-shrink-0 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7" />
</svg>
);
const ApparatusTruck3 = () => {
const prerequisites = [
"Candidate must be between 18 and 50 years of age, prior it his or her 51st birthday",
"Have a valid NJ State Driver License",
"Commit to Wednesday Night Training from 7pm-9pm",
"Interviewed by Chief and or President",
"Good moral character",
"Apparent adequate physical strength and stamina",
"Live or work in New Providence or within the area",
"Physical Well Being Certificate Exam by a physician",
"Complete background check including a fingerprint process with the New Providence Police Dept.",
];
return (
<section className="bg-white py-20">
<div id="full-member" className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid md:grid-cols-2 gap-12 items-center">
<div className="order-2 md:order-1">
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-6">Truck 3</h2>
<p className="text-gray-600 mb-6">The broad prerequisites for full membership are:</p>
<ul className="space-y-4">
{prerequisites.map((item, index) => (
<li key={index} className="flex items-start">
<CheckIcon />
<span className="text-gray-700">{item}</span>
</li>
))}
</ul>
</div>
<div className="order-1 md:order-2 text-center flex flex-col items-center justify-center bg-gray-100 p-8 rounded-lg shadow-inner">
<span className="text-7xl md:text-9xl font-extrabold text-red-600">18</span>
<span className="text-2xl md:text-3xl font-bold text-gray-800 mt-2">years old to apply</span>
</div>
</div>
</div>
</section>
);
};
export default ApparatusTruck3;

43
src/elements/Features.jsx Normal file
View File

@ -0,0 +1,43 @@
import React from 'react';
const Features = () => {
return (
// <section id="features" className="bg-white py-20 sm:py-24">
<section id="features" className="bg-gray-100 py-20">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 lg:gap-16 items-center">
<div className="space-y-6 bg-gray-100 p-8 rounded-lg shadow-lg transform hover:scale-105 transition-transform duration-300">
<h2 className="text-3xl font-extrabold text-gray-900">Join Our Team</h2>
<p className="text-lg text-gray-600">
Volunteer and be a part of something great. Full-Time or Junior Member opportunities available.
</p>
<a
href="/join"
target="_blank"
rel="noopener noreferrer"
className="inline-block bg-gray-800 text-white font-bold py-3 px-6 rounded-lg hover:bg-gray-700 transition-all duration-300 shadow-md"
>
JOIN NOW
</a>
</div>
<div className="space-y-6 bg-red-700 text-white p-8 rounded-lg shadow-lg transform hover:scale-105 transition-transform duration-300">
<h2 className="text-3xl font-extrabold">Donate</h2>
<p className="text-lg text-red-100">
We are 100% volunteer operated and are supported solely by your generous donations.
</p>
<a
href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=VCMML5H7AWYB2&source=url"
target="_blank"
rel="noopener noreferrer"
className="inline-block bg-white text-red-700 font-bold py-3 px-6 rounded-lg hover:bg-red-100 transition-all duration-300 shadow-md"
>
DONATE
</a>
</div>
</div>
</div>
</section>
);
};
export default Features;

View File

@ -0,0 +1,51 @@
const CheckIcon = () => (
<svg className="h-6 w-6 text-green-500 flex-shrink-0 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7" />
</svg>
);
const FullMemberDetails = () => {
const prerequisites = [
"Candidate must be between 18 and 50 years of age, prior it his or her 51st birthday",
"Have a valid NJ State Driver License",
"Commit to Wednesday Night Training from 7pm-9pm",
"Interviewed by Chief and or President",
"Good moral character",
"Apparent adequate physical strength and stamina",
"Live or work in New Providence or within the area",
"Physical Well Being Certificate Exam by a physician",
"Complete background check including a fingerprint process with the New Providence Police Dept.",
];
return (
<section className="bg-white py-20">
<div id="full-member" className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid md:grid-cols-2 gap-12 items-center">
<div className="order-2 md:order-1">
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-6">Full Member</h2>
<p className="text-gray-600 mb-6">The broad prerequisites for full membership are:</p>
<ul className="space-y-4">
{prerequisites.map((item, index) => (
<li key={index} className="flex items-start">
<CheckIcon />
<span className="text-gray-700">{item}</span>
</li>
))}
</ul>
<div className="mt-8">
<a href="/contact" className="inline-block bg-red-600 text-white font-bold py-3 px-8 rounded-lg shadow-lg hover:bg-red-700 transform hover:scale-105 transition-all duration-300">
APPLY NOW
</a>
</div>
</div>
<div className="order-1 md:order-2 text-center flex flex-col items-center justify-center bg-gray-100 p-8 rounded-lg shadow-inner">
<span className="text-7xl md:text-9xl font-extrabold text-red-600">18</span>
<span className="text-2xl md:text-3xl font-bold text-gray-800 mt-2">years old to apply</span>
</div>
</div>
</div>
</section>
);
};
export default FullMemberDetails;

33
src/elements/Hero.jsx Normal file
View File

@ -0,0 +1,33 @@
const Hero = () => {
return (
<section className="bg-gray-800 text-white">
{/* <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-24 md:py-32 text-center"> That way the background picture is stretching out to the whole block of the sections */}
<div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-24 md:py-32 text-center">
<div className="absolute inset-0 w-full h-full">
<img
className="w-full h-full rounded-xl object-cover opacity-20"
src="/assets/npfd-image-03.jpg"
alt="Firefighters in action"
/>
</div>
<h1 className="text-4xl md:text-6xl font-extrabold tracking-tight leading-tight mb-4 animate-fade-in-up">
Become a Member
</h1>
<p className="max-w-3xl mx-auto text-lg md:text-xl text-gray-300 mb-8 animate-fade-in-up" style={{ animationDelay: '0.2s' }}>
If you have a drive to help others and your community, we always welcome inquiries from potential members to join our team.
</p>
<div className="animate-fade-in-up" style={{ animationDelay: '0.4s' }}>
<a
href="/contact"
className="inline-block bg-red-600 text-white font-bold py-3 px-8 rounded-lg shadow-lg hover:bg-red-700 transform hover:scale-105 transition-all duration-300"
>
APPLY NOW
</a>
</div>
</div>
</section>
);
};
export default Hero;

View File

@ -0,0 +1,51 @@
const CheckIcon = () => (
<svg className="h-6 w-6 text-green-500 flex-shrink-0 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7" />
</svg>
);
const JuniorMemberDetails = () => {
const prerequisites = [
"16-18 years old; enrolled in high school and be at least a rising junior",
"Must be in good physical condition",
"Must have permission from a parent or guardian",
"Must be in good standing in school, community and have no problems with the law",
];
return (
<section className="bg-gray-100 py-20">
<div id="junior-member" className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid md:grid-cols-2 gap-12 items-center">
<div className="text-center flex flex-col items-center justify-center bg-white p-8 rounded-lg shadow-inner">
<span className="text-7xl md:text-9xl font-extrabold text-red-600">16</span>
<span className="text-2xl md:text-3xl font-bold text-gray-800 mt-2">years old to apply</span>
</div>
<div>
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-6">Junior Member</h2>
<p className="text-gray-600 mb-4">
The mission of our Junior program is to offer our high-school students an opportunity to serve in their community. Not required, but our goal is to have Juniors become Full Members after high school graduation.
</p>
<p className="text-gray-600 mb-6">
The Prerequisites are similar to Full Members, with the following differences:
</p>
<ul className="space-y-4">
{prerequisites.map((item, index) => (
<li key={index} className="flex items-start">
<CheckIcon />
<span className="text-gray-700">{item}</span>
</li>
))}
</ul>
<div className="mt-8">
<a href="/contact" className="inline-block bg-red-600 text-white font-bold py-3 px-8 rounded-lg shadow-lg hover:bg-red-700 transform hover:scale-105 transition-all duration-300">
APPLY NOW
</a>
</div>
</div>
</div>
</div>
</section>
);
};
export default JuniorMemberDetails;

View File

@ -0,0 +1,31 @@
const MembershipCard = ({ title, ageRange }) => (
<div className="bg-white p-8 rounded-lg shadow-lg text-center transform hover:-translate-y-2 transition-transform duration-300">
<h3 className="text-2xl font-bold text-gray-900 mb-2">{title}</h3>
<p className="text-gray-600">{ageRange}</p>
</div>
);
const MembershipTypes = () => {
return (
<section className="bg-gray-100 py-20">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-12">
<h2 className="text-3xl md:text-4xl font-bold text-gray-900">Join as a</h2>
<p className="mt-4 text-lg text-gray-600 max-w-2xl mx-auto">
If you are at least 18 years old and not enrolled in high school, you can volunteer to be a <span className="font-semibold">Full Member</span>. If you are at least 16 years old and enrolled in high school, you can volunteer for our <span className="font-semibold">Junior Program</span>.
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 max-w-3xl mx-auto">
<a href="#full-member" className="block">
<MembershipCard title="Full Member" ageRange="Between 18-50 years old" />
</a>
<a href="#junior-member" className="block">
<MembershipCard title="Junior Member" ageRange="Between 16-18 years old" />
</a>
</div>
</div>
</section>
);
};
export default MembershipTypes;

25
src/index.css Normal file
View File

@ -0,0 +1,25 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Force Tailwind to load */
@layer base {
:root {
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
background-color: rgb(17 24 39);
}

12
src/main.jsx Normal file
View File

@ -0,0 +1,12 @@
import React from "react";
import ReactDOM from "react-dom/client";
import { RouterProvider } from "react-router-dom";
import router from "./router";
import './index.css'
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);

38
src/router.jsx Normal file
View File

@ -0,0 +1,38 @@
import { createBrowserRouter } from "react-router-dom";
import App from "./App";
import Home from "./routes/Home";
import Team from "./routes/Team";
import Apparatus from "./routes/Apparatus";
import Join from "./routes/Join";
import Contact from "./routes/Contact";
const router = createBrowserRouter([
{
path: "/",
element: <App />,
children: [
{
index: true,
element: <Home />,
},
{
path: "team",
element: <Team />,
},
{
path: "apparatus",
element: <Apparatus />,
},
{
path: "join",
element: <Join />,
},
{
path: "contact",
element: <Contact />,
},
],
},
]);
export default router;

23
src/routes/Apparatus.jsx Normal file
View File

@ -0,0 +1,23 @@
import Hero from '../elements/Hero';
import ApparatusTruck3 from '../elements/ApparatusTruck3';
import ApparatusEngine4 from '../elements/ApparatusEngine4';
import ApparatusEngine2 from '../elements/ApparatusEngine2';
import ApparatusEngine1 from '../elements/ApparatusEngine1';
import ApparatusCar1 from '../elements/ApparatusCar1';
import ApparatusRescue8 from '../elements/ApparatusRescue8';
const Apparatus = () => {
return (
<div className="min-h-screen bg-gray-100 text-gray-800">
<Hero />
<ApparatusEngine1 />
<ApparatusEngine2 />
<ApparatusTruck3 />
<ApparatusEngine4 />
<ApparatusRescue8 />
<ApparatusCar1 />
</div>
);
};
export default Apparatus;

137
src/routes/Contact.jsx Normal file
View File

@ -0,0 +1,137 @@
import React, { useState } from 'react';
const Contact = () => {
const [formData, setFormData] = useState({
name: '',
email: '',
phone: '',
interest: '',
message: ''
});
const [submissionStatus, setSubmissionStatus] = useState(null); // 'success', 'error', 'submitting'
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prevState => ({
...prevState,
[name]: value
}));
};
const handleSubmit = async (e) => {
e.preventDefault(); // Prevent default form submission and page reload
setSubmissionStatus('submitting');
try {
const response = await fetch('/api/v1/contact', { // Replace with your API endpoint
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
});
if (response.ok) {
setSubmissionStatus('success');
setFormData({ name: '', email: '', phone: '', interest: '', message: '' }); // Clear form
} else {
setSubmissionStatus('error');
console.error('API submission failed:', response.statusText);
}
} catch (error) {
setSubmissionStatus('error');
console.error('Error submitting form:', error);
}
// e.preventDefault();
// // Handle form submission logic here
// console.log('Form submitted:', formData);
// alert('Thank you for your message!');
// setFormData({ name: '', email: '', phone: '', interest: '', message: '' });
};
return (
<div className="w-full">
<div className="bg-red-700 text-white text-center p-8">
<h2 className="text-2xl md:text-3xl font-bold tracking-tight">Want to help people?</h2>
<h1 className="text-4xl md:text-6xl font-extrabold mt-2">Join our team.</h1>
</div>
<div className="bg-white py-12 sm:py-16">
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-16">
{/* Contact Form Section */}
<div className="bg-gray-50 p-8 rounded-lg shadow-lg">
<h2 className="text-3xl font-bold text-gray-900 mb-6 border-b-2 border-red-600 pb-2">APPLY HERE</h2>
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<label htmlFor="interest" className="block text-sm font-medium text-gray-700">I would like to...</label>
<select id="interest" name="interest" value={formData.interest} onChange={handleChange} required className="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-red-500 focus:border-red-500 sm:text-sm rounded-md">
<option value="" disabled>Select an option</option>
<option>Ask a Question</option>
<option>Apply as a Full Member</option>
<option>Apply as a Junior Member</option>
</select>
</div>
<div>
<label htmlFor="name" className="block text-sm font-medium text-gray-700">Name</label>
<input type="text" name="name" id="name" value={formData.name} onChange={handleChange} required className="mt-1 block w-full px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-red-500 focus:border-red-500 sm:text-sm" />
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700">Email Address</label>
<input type="email" name="email" id="email" value={formData.email} onChange={handleChange} required className="mt-1 block w-full px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-red-500 focus:border-red-500 sm:text-sm" />
</div>
<div>
<label htmlFor="phone" className="block text-sm font-medium text-gray-700">Phone Number</label>
<input type="tel" name="phone" id="phone" value={formData.phone} onChange={handleChange} className="mt-1 block w-full px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-red-500 focus:border-red-500 sm:text-sm" />
</div>
<div>
<label htmlFor="message" className="block text-sm font-medium text-gray-700">Message</label>
<textarea id="message" name="message" rows="4" value={formData.message} onChange={handleChange} required className="mt-1 block w-full px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-red-500 focus:border-red-500 sm:text-sm"></textarea>
</div>
<div>
<button type="submit" disabled={submissionStatus === 'submitting'} className="w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-lg font-medium text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 transform hover:scale-105 transition-transform duration-200">
{submissionStatus === 'submitting' ? 'Sending...' : 'SUBMIT'}
</button>
{submissionStatus === 'success' && (
<p style={{ color: 'green' }}>Message sent successfully!</p>
)}
{submissionStatus === 'error' && (
<p style={{ color: 'red' }}>Error sending message. Please try again.</p>
)}
</div>
</form>
</div>
{/* Location and Emergency Info Section */}
<div className="space-y-8">
<div className="bg-gray-900 text-white p-8 rounded-lg shadow-lg">
<h2 className="text-3xl font-bold mb-4 border-b-2 border-red-600 pb-2">Our Location</h2>
<p className="text-lg">175 Floral Avenue</p>
<p className="text-lg">New Providence, NJ 07974</p>
<a href="mailto:npfdfireofficers@gmail.com" className="mt-4 inline-block text-red-400 hover:text-red-300 transition-colors duration-200">
npfdfireofficers@gmail.com
</a>
</div>
<div className="bg-yellow-400 border-l-4 border-yellow-600 text-yellow-900 p-6 rounded-r-lg shadow-lg">
<h2 className="text-2xl font-extrabold">Call 911 if you are having an emergency.</h2>
</div>
<div className="text-center text-xs text-gray-500 mt-6">
<p>This site is protected by reCAPTCHA and the Google</p>
<p>
<a href="https://www.google.com/intl/en/policies/privacy/" className="underline hover:text-red-600">Privacy Policy</a> and
<a href="https://www.google.com/intl/en/policies/terms/" className="underline hover:text-red-600"> Terms of Service</a> apply.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default Contact;

37
src/routes/Home.jsx Normal file
View File

@ -0,0 +1,37 @@
import Features from '../elements/Features';
const Home = () => {
return (
<div className="relative bg-gray-800 text-white min-h-screen">
<div className="relative max-w-7xl mx-auto py-24 px-4 sm:py-32 sm:px-6 lg:px-8 text-center">
<div className="absolute inset-0">
<img
className="w-full h-full rounded-xl object-cover opacity-20"
src="/assets/npfd-image-02.jpg"
alt="Firefighters in action"
/>
</div>
<h1 className="text-4xl font-extrabold tracking-tight text-white sm:text-5xl lg:text-6xl animate-fade-in-up">
When fire or accidents strike,<br/>we're here to help.
</h1>
<p className="mt-6 max-w-3xl mx-auto text-xl text-gray-300 animate-fade-in-up animation-delay-300">
New Providence's All-Volunteer Fire and Rescue Services, on call 24/7.
</p>
<div className="mt-12 animate-fade-in-up animation-delay-600">
<a
href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=VCMML5H7AWYB2&source=url"
target="_blank"
rel="noopener noreferrer"
className="inline-block bg-red-600 border border-transparent rounded-md py-3 px-8 font-medium text-white hover:bg-red-700 transition-transform transform hover:scale-105"
>
Donate to Show Your Support
</a>
</div>
</div>
<Features />
</div>
);
};
export default Home;

17
src/routes/Join.jsx Normal file
View File

@ -0,0 +1,17 @@
import Hero from '../elements/Hero';
import MembershipTypes from '../elements/MembershipTypes';
import FullMemberDetails from '../elements/FullMemberDetails';
import JuniorMemberDetails from '../elements/JuniorMemberDetails';
const Home = () => {
return (
<div className="min-h-screen bg-gray-100 text-gray-800">
<Hero />
<MembershipTypes />
<FullMemberDetails />
<JuniorMemberDetails />
</div>
);
};
export default Home;

177
src/routes/Team.jsx Normal file
View File

@ -0,0 +1,177 @@
const OfficerCard = ({ title, name }) => (
<div className="bg-white p-6 rounded-lg shadow-lg text-center transform hover:-translate-y-2 transition-transform duration-300">
<p className="text-md font-semibold text-gray-600 uppercase tracking-wider">{title}</p>
<p className="text-xl font-bold text-gray-900 mt-1">{name}</p>
</div>
);
const Team = () => {
const fireOfficers = [
{ title: "Fire Chief", name: "John Signorello" },
{ title: "Deputy Chief #1", name: "Will Zagorski" },
{ title: "Deputy Chief #2", name: "Drew Vignali" },
{ title: "Fire Official", name: "Edward Nasto" },
{ title: "Captain #1", name: "Vincent J. Wycko" },
{ title: "Captain #2", name: "Deborah Golden" },
{ title: "Captain #3", name: "Jake Vignali" },
{ title: "Captain #4", name: "Ben Hasenkoff" },
];
const hoseCompanyOfficers = [
{ title: "President", name: "Mary Rose Piana" },
{ title: "Vice President", name: "Glenn Sonnet" },
{ title: "Treasurer", name: "William Schmeelck" },
{ title: "Secretary", name: "Alexander Savin" },
];
const members = [
{ title: "Firefighter", name: "Aleksandr Alekseev" },
{ title: "Warden", name: "Lawrence J. Armstrong" },
{ title: "Firefighter", name: "David A. Buell" },
{ title: "Firefighter", name: "Sara D. Costa" },
{ title: "Firefighter", name: "John A. Grouss" },
{ title: "Firefighter", name: "Christopher T. Hasenkopf" },
{ title: "Warden", name: "Darrell Jones" },
{ title: "Firefighter", name: "Raghava Kandibedala" },
{ title: "Firefighter", name: "Bruce Lessing" },
{ title: "Firefighter", name: "Scott Lessing" },
{ title: "Firefighter", name: "Alexander P. Melhuish" },
{ title: "Firefighter", name: "Robert P. Mercado" },
{ title: "Firefighter", name: "Brian A. Morgan" },
{ title: "Firefighter", name: "Robert Munoz" },
{ title: "Firefighter", name: "Cooper Nelson" },
{ title: "Firefighter", name: "Daniel G. Nelson" },
{ title: "Firefighter", name: "Christopher B. Oh" },
{ title: "Firefighter", name: "David R. Palatini" },
{ title: "Firefighter", name: "Ralph J. Parlapiano" },
{ title: "Firefighter", name: "Scott J. Paxton" },
{ title: "Firefighter", name: "Clint A. Pazdera" },
{ title: "Firefighter", name: "Alexander Pereira" },
{ title: "Firefighter", name: "Sean M. Pile" },
{ title: "Firefighter", name: "Marcus G. Reichart" },
{ title: "Firefighter", name: "Peter Popescu" },
{ title: "Firefighter", name: "Nisim Sahar" },
{ title: "Firefighter", name: "Jessica Seib" },
{ title: "Firefighter", name: "Kevin Shan" },
{ title: "Firefighter", name: "Chandrasekhar Sidda Reddy" },
{ title: "Warden", name: "James Swanton" },
{ title: "Firefighter", name: "Joseph D. Vignali" },
{ title: "Firefighter", name: "William S. Vignali" },
{ title: "Firefighter", name: "John M. Yohn" },
];
const juniors = [
{ title: "Junior Firefighter", name: "Maxim A. Savin" },
];
const images = [
"https://newprovidencefire.org/wp-content/uploads/2020/09/IMG_0930-scaled.jpg",
"https://newprovidencefire.org/wp-content/uploads/2020/09/IMG_0914-scaled.jpg",
"https://newprovidencefire.org/wp-content/uploads/2020/09/firetruck.jpg",
"https://newprovidencefire.org/wp-content/uploads/2020/09/IMG_0801.png",
"https://newprovidencefire.org/wp-content/uploads/2020/09/hose.jpg",
"https://newprovidencefire.org/wp-content/uploads/2020/09/IMG_0923.png",
];
return (
<div className="bg-gray-100">
<section className="bg-gray-800 text-white">
{/* <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-24 md:py-32 text-center"> That way the background picture is stretching out to the whole block of the sections */}
<div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-24 md:py-32 text-center">
<div className="absolute inset-0 w-full h-full">
<img
className="w-full h-full rounded-xl object-cover opacity-20"
src="/assets/npfd-image-03.jpg"
alt="Firefighters in action"
/>
</div>
<h1 className="text-4xl md:text-6xl font-extrabold tracking-tight leading-tight mb-4 animate-fade-in-up">
Our Team
</h1>
<p className="max-w-3xl mx-auto text-lg md:text-xl text-gray-300 mb-8 animate-fade-in-up" style={{ animationDelay: '0.2s' }}>
The New Providence Fire Department is a non-profit organization comprised of volunteers who are dedicated to providing fire protection and rescue services to those in need in New Providence, New Jersey.
</p>
</div>
</section>
{/* Officers Section */}
<section className="py-20 bg-gray-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center">
<h2 className="text-3xl md:text-4xl font-bold text-gray-900">New Providence Fire Department Officers</h2>
</div>
{/* Fire Officers List */}
<div className="mt-20">
<h3 className="text-2xl md:text-3xl font-bold text-red-600 text-center mb-10">Fire Officers</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
{fireOfficers.map(officer => <OfficerCard key={officer.name} {...officer} />)}
</div>
</div>
{/* Hose Co Officers List */}
<div className="mt-16">
<h3 className="text-2xl md:text-3xl font-bold text-red-600 text-center mb-10">Hose Company Officers</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
{hoseCompanyOfficers.map(officer => <OfficerCard key={officer.name} {...officer} />)}
</div>
</div>
{/* Members List */}
<div className="mt-16">
<h3 className="text-2xl md:text-3xl font-bold text-red-600 text-center mb-10">Members</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
{members.map(member => <OfficerCard key={member.name} {...member} />)}
</div>
</div>
{/* Juniors List */}
<div className="mt-16">
<h3 className="text-2xl md:text-3xl font-bold text-red-600 text-center mb-10">Juniors</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
{juniors.map(junior => <OfficerCard key={junior.name} {...junior} />)}
</div>
</div>
</div>
</section>
{/* Image Gallery */}
<section className="bg-white py-20">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{images.map((src, index) => (
<div key={index} className="overflow-hidden rounded-lg shadow-lg aspect-w-1 aspect-h-1">
<img src={src} alt={`Fire department gallery image \${index + 1}`} className="w-full h-full object-cover transform hover:scale-105 transition-transform duration-300" />
</div>
))}
</div>
</div>
</section>
{/* CTA Section */}
<section className="bg-gray-800">
<div className="max-w-4xl mx-auto py-16 px-4 sm:px-6 lg:px-8 text-center">
<h2 className="text-3xl font-extrabold text-white sm:text-4xl">
Your 24/7 fire protection help in New Providence.
</h2>
<p className="mt-4 text-lg text-gray-300">
Your support is appreciated.
</p>
<a
href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=VCMML5H7AWYB2&source=url"
target="_blank"
rel="noopener noreferrer"
className="mt-8 w-full inline-flex items-center justify-center px-6 py-3 border border-transparent rounded-md shadow-sm text-base font-medium text-white bg-red-600 hover:bg-red-700 sm:w-auto transform hover:scale-105 transition-all duration-300"
>
Donate Now
</a>
</div>
</section>
</div>
);
};
export default Team;

11
tailwind.config.js Normal file
View File

@ -0,0 +1,11 @@
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}

14
vite.config.js Normal file
View File

@ -0,0 +1,14 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// E2B-compatible Vite configuration
export default defineConfig({
plugins: [react()],
server: {
host: '0.0.0.0',
port: 3000,
strictPort: true,
hmr: false,
allowedHosts: ['localhost', '127.0.0.1']
}
})