first commit
This commit is contained in:
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal 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
8
Makefile
Normal 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
3
api/go.mod
Normal file
@ -0,0 +1,3 @@
|
||||
module npfd-api
|
||||
|
||||
go 1.24
|
105
api/main.go
Normal file
105
api/main.go
Normal 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
12
index.html
Normal 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
2736
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
23
package.json
Normal file
23
package.json
Normal 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
6
postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
BIN
public/assets/npfd-image-01.jpg
Normal file
BIN
public/assets/npfd-image-01.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 282 KiB |
BIN
public/assets/npfd-image-02.jpg
Normal file
BIN
public/assets/npfd-image-02.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 260 KiB |
BIN
public/assets/npfd-image-03.jpg
Normal file
BIN
public/assets/npfd-image-03.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 178 KiB |
BIN
public/assets/npfd-image-04.jpg
Normal file
BIN
public/assets/npfd-image-04.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 252 KiB |
BIN
public/assets/profile-member-00.jpg
Normal file
BIN
public/assets/profile-member-00.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 193 KiB |
16
src/App.jsx
Normal file
16
src/App.jsx
Normal 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
35
src/components/Footer.jsx
Normal 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">
|
||||
© {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
105
src/components/Header.jsx
Normal 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>
|
||||
))} */}
|
46
src/elements/ApparatusCar1.jsx
Normal file
46
src/elements/ApparatusCar1.jsx
Normal 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;
|
46
src/elements/ApparatusEngine1.jsx
Normal file
46
src/elements/ApparatusEngine1.jsx
Normal 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;
|
46
src/elements/ApparatusEngine2.jsx
Normal file
46
src/elements/ApparatusEngine2.jsx
Normal 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;
|
46
src/elements/ApparatusEngine4.jsx
Normal file
46
src/elements/ApparatusEngine4.jsx
Normal 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;
|
46
src/elements/ApparatusRescue8.jsx
Normal file
46
src/elements/ApparatusRescue8.jsx
Normal 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;
|
46
src/elements/ApparatusTruck3.jsx
Normal file
46
src/elements/ApparatusTruck3.jsx
Normal 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
43
src/elements/Features.jsx
Normal 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;
|
51
src/elements/FullMemberDetails.jsx
Normal file
51
src/elements/FullMemberDetails.jsx
Normal 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
33
src/elements/Hero.jsx
Normal 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;
|
51
src/elements/JuniorMemberDetails.jsx
Normal file
51
src/elements/JuniorMemberDetails.jsx
Normal 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;
|
31
src/elements/MembershipTypes.jsx
Normal file
31
src/elements/MembershipTypes.jsx
Normal 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
25
src/index.css
Normal 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
12
src/main.jsx
Normal 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
38
src/router.jsx
Normal 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
23
src/routes/Apparatus.jsx
Normal 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
137
src/routes/Contact.jsx
Normal 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
37
src/routes/Home.jsx
Normal 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
17
src/routes/Join.jsx
Normal 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
177
src/routes/Team.jsx
Normal 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
11
tailwind.config.js
Normal 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
14
vite.config.js
Normal 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']
|
||||
}
|
||||
})
|
Reference in New Issue
Block a user