Add gallery system to the website

This commit is contained in:
TheMaddax 2025-10-14 09:46:54 -06:00
parent 3294fc2cc6
commit 9733d4099f
4 changed files with 190 additions and 8 deletions

View file

@ -9,6 +9,7 @@ import Footer from './components/Footer';
import Bylaws from './components/Bylaws'; import Bylaws from './components/Bylaws';
import Sponsors from './components/Sponsors'; import Sponsors from './components/Sponsors';
import MeetBoard from './components/Meet-Board'; import MeetBoard from './components/Meet-Board';
import Gallery from './components/Gallery';
/* import ComingSoon from './components/ComingSoon'; */ /* import ComingSoon from './components/ComingSoon'; */
import JoinMCDi from './components/JoinMCDi'; import JoinMCDi from './components/JoinMCDi';
@ -26,6 +27,7 @@ const App: React.FC = () => {
<Route path="/bylaws" element={<Bylaws />} /> <Route path="/bylaws" element={<Bylaws />} />
<Route path="/sponsors" element={<Sponsors />} /> <Route path="/sponsors" element={<Sponsors />} />
<Route path="/meet-board" element={<MeetBoard />} /> <Route path="/meet-board" element={<MeetBoard />} />
<Route path="/gallery" element={<Gallery />} />
<Route path="/joinmcdi" element={<JoinMCDi />} /> <Route path="/joinmcdi" element={<JoinMCDi />} />
</Routes> </Routes>
</main> </main>
@ -35,4 +37,4 @@ const App: React.FC = () => {
); );
}; };
export default App; export default App;

View file

@ -0,0 +1,165 @@
import React, { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
interface GalleryItem {
id: number;
imageUrl: string;
caption: string;
alt: string;
}
// Placeholder data using board member headshots
const galleryData: GalleryItem[] = [
{
id: 1,
imageUrl: '/headshots/tessa.jpg',
caption: 'Tessa Williams - President',
alt: 'Tessa Williams headshot',
},
{
id: 2,
imageUrl: '/headshots/rita.jpg',
caption: 'Rita Sommers-Flanagan - Vice President',
alt: 'Rita Sommers-Flanagan headshot',
},
{
id: 3,
imageUrl: '/headshots/aubz.jpg',
caption: 'Aubz Orr - Secretary',
alt: 'Aubz Orr headshot',
},
{
id: 4,
imageUrl: '/headshots/eliza.jpg',
caption: 'Eliza Gutierrez - Treasurer',
alt: 'Eliza Gutierrez headshot',
},
{
id: 5,
imageUrl: '/headshots/skyla.jpg',
caption: 'Skyla Heaton - Board Member',
alt: 'Skyla Heaton headshot',
},
{
id: 6,
imageUrl: '/headshots/kevin.jpg',
caption: 'Kevin Wolff - Board Member',
alt: 'Kevin Wolff headshot',
},
];
const Gallery: React.FC = () => {
const [selectedImage, setSelectedImage] = useState<GalleryItem | null>(null);
const openImage = (item: GalleryItem) => {
setSelectedImage(item);
};
const closeImage = () => {
setSelectedImage(null);
};
return (
<div className="container mx-auto px-4 py-8">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
<h1 className="text-4xl font-bold text-[#8C1D40] mb-8">Photo Gallery</h1>
{/* Photo Grid */}
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
{galleryData.map((item) => (
<motion.div
key={item.id}
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.3 }}
whileHover={{ scale: 1.05 }}
className="cursor-pointer"
onClick={() => openImage(item)}
>
<div className="bg-white rounded-lg shadow-lg overflow-hidden hover:shadow-xl transition-shadow duration-300">
<div className="aspect-square overflow-hidden">
<img
src={item.imageUrl}
alt={item.alt}
className="w-full h-full object-cover"
/>
</div>
<div className="p-3 bg-[#6B1631]">
<p className="text-yellow-400 text-sm font-semibold text-center truncate">
{item.caption}
</p>
</div>
</div>
</motion.div>
))}
</div>
</motion.div>
{/* Image Modal */}
<AnimatePresence>
{selectedImage && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50 p-4"
onClick={closeImage}
>
<motion.div
initial={{ scale: 0.8, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.8, opacity: 0 }}
transition={{ type: 'spring', damping: 25, stiffness: 300 }}
className="relative max-w-5xl max-h-[90vh] flex flex-col"
onClick={(e) => e.stopPropagation()}
>
{/* Close Button */}
<button
onClick={closeImage}
className="absolute -top-12 right-0 text-white hover:text-yellow-400 transition-colors duration-200 z-10"
aria-label="Close image"
>
<svg
className="w-10 h-10"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
{/* Image Container */}
<div className="bg-white rounded-t-lg overflow-hidden shadow-2xl">
<img
src={selectedImage.imageUrl}
alt={selectedImage.alt}
className="max-w-full max-h-[70vh] w-auto h-auto mx-auto object-contain"
/>
</div>
{/* Caption */}
<div className="bg-[#6B1631] rounded-b-lg p-4 border-2 border-black shadow-2xl">
<p className="text-yellow-400 text-xl font-bold text-center">
{selectedImage.caption}
</p>
</div>
</motion.div>
</motion.div>
)}
</AnimatePresence>
</div>
);
};
export default Gallery;

View file

@ -74,6 +74,7 @@ const Header: React.FC = () => {
<li><NavItem href="/" onClick={() => setIsMenuOpen(false)}>Home</NavItem></li> <li><NavItem href="/" onClick={() => setIsMenuOpen(false)}>Home</NavItem></li>
<li><NavItem href="/about" onClick={() => setIsMenuOpen(false)}>About MCDi</NavItem></li> <li><NavItem href="/about" onClick={() => setIsMenuOpen(false)}>About MCDi</NavItem></li>
<li><NavItem href="/calendar" onClick={() => setIsMenuOpen(false)}>Calendar</NavItem></li> <li><NavItem href="/calendar" onClick={() => setIsMenuOpen(false)}>Calendar</NavItem></li>
<li><NavItem href="/gallery" onClick={() => setIsMenuOpen(false)}>Gallery</NavItem></li>
<li><NavItem href="/joinmcdi" onClick={() => setIsMenuOpen(false)}>Join MCDi</NavItem></li> <li><NavItem href="/joinmcdi" onClick={() => setIsMenuOpen(false)}>Join MCDi</NavItem></li>
</ul> </ul>
</motion.div> </motion.div>
@ -156,6 +157,7 @@ const AboutDropdown: React.FC = () => {
<DropdownItem href="/meet-board">Meet MCDi Board</DropdownItem> <DropdownItem href="/meet-board">Meet MCDi Board</DropdownItem>
<DropdownItem href="/bylaws">Bylaws</DropdownItem> <DropdownItem href="/bylaws">Bylaws</DropdownItem>
<DropdownItem href="/minutes">Minutes</DropdownItem> <DropdownItem href="/minutes">Minutes</DropdownItem>
<DropdownItem href="/gallery">Gallery</DropdownItem>
<DropdownItem href="/sponsors">Sponsors</DropdownItem> <DropdownItem href="/sponsors">Sponsors</DropdownItem>
</div> </div>
</motion.div> </motion.div>

View file

@ -1,10 +1,21 @@
# Active Context # Active Context
## Current Task ## Current Task
Completed: Made yellow donation button visible on mobile header Completed: Created new Gallery page with photo grid and modal viewer
## Recent Changes ## Recent Changes
1. Modified Header.tsx to display donation button on mobile: 1. Created new Gallery component (Gallery.tsx):
- Responsive photo grid layout (1-4 columns based on screen size)
- Clickable images that enlarge in a modal viewer
- Yellow text captions on red background with black border
- Smooth animations using Framer Motion
- Currently using board member headshots as placeholder images
- Modal features: click outside to close, X button, spring animation
- Maintains website's color scheme (#6B1631 red, yellow-400 text)
- Added to navigation: "About MCDi" dropdown and mobile menu
- Route added: /gallery
2. Previous: Modified Header.tsx to display donation button on mobile:
- Added permanent yellow donation button below hamburger menu on mobile devices - Added permanent yellow donation button below hamburger menu on mobile devices
- Button is always visible in the red header area (no need to open menu) - Button is always visible in the red header area (no need to open menu)
- Removed duplicate donation button from inside mobile menu dropdown - Removed duplicate donation button from inside mobile menu dropdown
@ -35,11 +46,13 @@ Completed: Made yellow donation button visible on mobile header
- All previous changes successfully deployed to production - All previous changes successfully deployed to production
## Next Steps ## Next Steps
1. Commit and push Header.tsx changes to Git repository 1. Wait for website owner to provide actual gallery photos
2. Deploy changes through Portainer (pull and redeploy) 2. Replace placeholder headshots with real gallery photos
3. Verify mobile donation button appears correctly on production 3. Update captions with appropriate descriptions
4. Verify desktop layout remains unchanged 4. Commit all changes (Gallery.tsx, App.tsx, Header.tsx) to Git
5. Test donation button functionality on mobile devices 5. Deploy changes through Portainer (pull and redeploy)
6. Test gallery functionality on production (desktop and mobile)
7. Consider adding video support to gallery in future
## Solution Summary ## Solution Summary
- Problem: Portainer repository deployments require stack.env file in Git repo - Problem: Portainer repository deployments require stack.env file in Git repo