Add gallery system to the website
This commit is contained in:
parent
3294fc2cc6
commit
9733d4099f
4 changed files with 190 additions and 8 deletions
|
|
@ -9,6 +9,7 @@ import Footer from './components/Footer';
|
|||
import Bylaws from './components/Bylaws';
|
||||
import Sponsors from './components/Sponsors';
|
||||
import MeetBoard from './components/Meet-Board';
|
||||
import Gallery from './components/Gallery';
|
||||
/* import ComingSoon from './components/ComingSoon'; */
|
||||
import JoinMCDi from './components/JoinMCDi';
|
||||
|
||||
|
|
@ -26,6 +27,7 @@ const App: React.FC = () => {
|
|||
<Route path="/bylaws" element={<Bylaws />} />
|
||||
<Route path="/sponsors" element={<Sponsors />} />
|
||||
<Route path="/meet-board" element={<MeetBoard />} />
|
||||
<Route path="/gallery" element={<Gallery />} />
|
||||
<Route path="/joinmcdi" element={<JoinMCDi />} />
|
||||
</Routes>
|
||||
</main>
|
||||
|
|
@ -35,4 +37,4 @@ const App: React.FC = () => {
|
|||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
export default App;
|
||||
|
|
|
|||
165
client/src/components/Gallery.tsx
Normal file
165
client/src/components/Gallery.tsx
Normal 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;
|
||||
|
|
@ -74,6 +74,7 @@ const Header: React.FC = () => {
|
|||
<li><NavItem href="/" onClick={() => setIsMenuOpen(false)}>Home</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="/gallery" onClick={() => setIsMenuOpen(false)}>Gallery</NavItem></li>
|
||||
<li><NavItem href="/joinmcdi" onClick={() => setIsMenuOpen(false)}>Join MCDi</NavItem></li>
|
||||
</ul>
|
||||
</motion.div>
|
||||
|
|
@ -156,6 +157,7 @@ const AboutDropdown: React.FC = () => {
|
|||
<DropdownItem href="/meet-board">Meet MCDi Board</DropdownItem>
|
||||
<DropdownItem href="/bylaws">Bylaws</DropdownItem>
|
||||
<DropdownItem href="/minutes">Minutes</DropdownItem>
|
||||
<DropdownItem href="/gallery">Gallery</DropdownItem>
|
||||
<DropdownItem href="/sponsors">Sponsors</DropdownItem>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,21 @@
|
|||
# Active Context
|
||||
|
||||
## Current Task
|
||||
Completed: Made yellow donation button visible on mobile header
|
||||
Completed: Created new Gallery page with photo grid and modal viewer
|
||||
|
||||
## 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
|
||||
- Button is always visible in the red header area (no need to open menu)
|
||||
- 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
|
||||
|
||||
## Next Steps
|
||||
1. Commit and push Header.tsx changes to Git repository
|
||||
2. Deploy changes through Portainer (pull and redeploy)
|
||||
3. Verify mobile donation button appears correctly on production
|
||||
4. Verify desktop layout remains unchanged
|
||||
5. Test donation button functionality on mobile devices
|
||||
1. Wait for website owner to provide actual gallery photos
|
||||
2. Replace placeholder headshots with real gallery photos
|
||||
3. Update captions with appropriate descriptions
|
||||
4. Commit all changes (Gallery.tsx, App.tsx, Header.tsx) to Git
|
||||
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
|
||||
- Problem: Portainer repository deployments require stack.env file in Git repo
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue