From e47efca8475d178ffc93d8536891670283ad9ce7 Mon Sep 17 00:00:00 2001 From: TheMaddax Date: Mon, 14 Oct 2024 20:05:06 -0500 Subject: [PATCH] Add in support for mobile browsers --- client/package.json | 2 + client/src/App.tsx | 2 +- client/src/components/Calendar.tsx | 152 ++++++++++++--------------- client/src/components/EventCard.tsx | 23 +--- client/src/components/Footer.tsx | 20 ++-- client/src/components/Header.tsx | 47 +++++++-- client/src/components/Home.tsx | 26 ++--- client/src/components/Meet-Board.tsx | 33 +++--- client/src/components/Sponsors.tsx | 38 +++---- client/src/index.css | 106 ++++++++++++++++++- client/tailwind.config.js | 38 +++++-- 11 files changed, 301 insertions(+), 186 deletions(-) diff --git a/client/package.json b/client/package.json index a191fad..ef352ae 100644 --- a/client/package.json +++ b/client/package.json @@ -41,6 +41,8 @@ ] }, "devDependencies": { + "@tailwindcss/forms": "^0.5.3", + "@tailwindcss/typography": "^0.5.9", "postcss-cli": "^8.3.1", "@types/styled-components": "^5.1.29", "autoprefixer": "^10.4.16", diff --git a/client/src/App.tsx b/client/src/App.tsx index 41f8782..65e6466 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -16,7 +16,7 @@ const App: React.FC = () => {
-
+
} /> } /> diff --git a/client/src/components/Calendar.tsx b/client/src/components/Calendar.tsx index ba3fc5c..5b19a04 100644 --- a/client/src/components/Calendar.tsx +++ b/client/src/components/Calendar.tsx @@ -4,25 +4,41 @@ import EventCard from './EventCard'; import { parseISO, startOfDay, isSameDay, isAfter, format, startOfMonth } from 'date-fns'; import { events, Event } from '../eventData'; -const formatTime = (dateString: string, time: string): string => { +export const formatTime = (dateString: string, time: string): string => { const date = parseISO(dateString); const [hours, minutes] = time.split(':'); const eventDate = new Date(date.setHours(parseInt(hours, 10), parseInt(minutes, 10))); - return format(eventDate, 'MMMM d, yyyy h:mm a'); + return format(eventDate, 'h:mm a'); }; -const EventPopup: React.FC<{ event: Event; position: { top: number; left: number } }> = ({ event, position }) => ( +export const formatDateRange = (startDate: string, startTime: string, endDate: string, endTime: string): string => { + const start = parseISO(`${startDate}T${startTime}`); + const end = parseISO(`${endDate}T${endTime}`); + + if (isSameDay(start, end)) { + return `${format(start, 'MMMM d, yyyy')} ${formatTime(startDate, startTime)} - ${formatTime(endDate, endTime)}`; + } else { + return `${format(start, 'MMMM d, yyyy h:mm a')} - ${format(end, 'MMMM d, yyyy h:mm a')}`; + } +}; + +const EventPopup: React.FC<{ event: Event; position: { top: number; left: number }; onClose: () => void }> = ({ event, position, onClose }) => ( -

{event.title}

-

{formatTime(event.date, event.startTime)} - {formatTime(event.date, event.endTime)}

-

{event.description}

-

Contact: {event.pointOfContact}

+ e.stopPropagation()} + > +

{event.title}

+

{formatDateRange(event.date, event.startTime, event.date, event.endTime)}

+

{event.description}

+

Contact: {event.pointOfContact}

+
); @@ -32,10 +48,23 @@ const Calendar: React.FC = () => { return startOfMonth(now); }); const [selectedEvent, setSelectedEvent] = useState(null); - const [popupPosition, setPopupPosition] = useState({ top: 0, left: 0 }); - const [isPopupClosing, setIsPopupClosing] = useState(false); const calendarRef = useRef(null); + useEffect(() => { + const handleClickOutside = (event: MouseEvent | TouchEvent) => { + if (selectedEvent && calendarRef.current && !calendarRef.current.contains(event.target as Node)) { + setSelectedEvent(null); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + document.addEventListener('touchstart', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + document.removeEventListener('touchstart', handleClickOutside); + }; + }, [selectedEvent]); + const isEventUpcoming = (eventDate: string) => { const today = startOfDay(new Date()); const parsedEventDate = startOfDay(parseISO(eventDate)); @@ -60,53 +89,8 @@ const Calendar: React.FC = () => { setCurrentMonth(new Date(currentMonth.getFullYear(), currentMonth.getMonth() - 1, 1)); }; - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if (calendarRef.current && !calendarRef.current.contains(event.target as Node)) { - setSelectedEvent(null); - } else if (selectedEvent) { - setSelectedEvent(null); - setIsPopupClosing(true); - setTimeout(() => setIsPopupClosing(false), 0); - } - }; - - document.addEventListener('mousedown', handleClickOutside); - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, [selectedEvent]); - - useEffect(() => { - const now = new Date(); - const currentMonthEvents = events.filter(event => { - const eventDate = parseISO(event.date); - return eventDate.getMonth() === now.getMonth() && eventDate.getFullYear() === now.getFullYear(); - }); - - if (currentMonthEvents.length === 0) { - // If no events in the current month, find the next month with events - let nextMonth = now; - while (true) { - nextMonth = new Date(nextMonth.getFullYear(), nextMonth.getMonth() + 1, 1); - const nextMonthEvents = events.filter(event => { - const eventDate = parseISO(event.date); - return eventDate.getMonth() === nextMonth.getMonth() && eventDate.getFullYear() === nextMonth.getFullYear(); - }); - if (nextMonthEvents.length > 0) { - setCurrentMonth(startOfMonth(nextMonth)); - break; - } - } - } - }, []); - - const handleEventClick = (event: Event, e: React.MouseEvent) => { - if (!isPopupClosing) { - const rect = (e.target as HTMLElement).getBoundingClientRect(); - setPopupPosition({ top: rect.bottom, left: rect.left }); - setSelectedEvent(event); - } + const handleEventClick = (event: Event) => { + setSelectedEvent(event); }; const upcomingEvents = events @@ -114,7 +98,7 @@ const Calendar: React.FC = () => { .sort((a, b) => parseISO(a.date).getTime() - parseISO(b.date).getTime()); return ( -
+
{ > ← -

+

{monthNames[currentMonth.getMonth()]} {currentMonth.getFullYear()}

{
- {['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].map(day => ( -
+ {['S', 'M', 'T', 'W', 'T', 'F', 'S'].map(day => ( +
{day}
))} {emptyDays.map(day => ( -
+
))} {days.map(day => { const currentDate = new Date(currentMonth.getFullYear(), currentMonth.getMonth(), day); @@ -153,18 +137,20 @@ const Calendar: React.FC = () => { return ( - {day} + {day} {dayEvents.map((event, index) => (
handleEventClick(event, e)} + className="text-xs bg-[#8C1D40] text-white p-1 mt-1 rounded w-full truncate" + onClick={() => handleEventClick(event)} > -
{event.title}
-
{`${formatTime(event.date, event.startTime).split(' ')[3]} - ${formatTime(event.date, event.endTime).split(' ')[3]}`}
+
{event.title}
+
+ {formatTime(event.date, event.startTime)} - {formatTime(event.date, event.endTime)} +
))}
@@ -175,27 +161,27 @@ const Calendar: React.FC = () => { {selectedEvent && ( setSelectedEvent(null)} /> )}
-

Upcoming Events

-
- {upcomingEvents.map((event, index) => ( - - ))} -
+

Upcoming Events

+
+ {upcomingEvents.map((event, index) => ( + + ))}
+
); }; diff --git a/client/src/components/EventCard.tsx b/client/src/components/EventCard.tsx index d0724b3..f84b657 100644 --- a/client/src/components/EventCard.tsx +++ b/client/src/components/EventCard.tsx @@ -1,38 +1,25 @@ import React from 'react'; import { motion } from 'framer-motion'; -import { format } from 'date-fns'; +import { Event } from '../eventData'; interface EventCardProps { - event: { - date: string; - title: string; - description: string; - formattedStartTime: string; - formattedEndTime: string; - pointOfContact: string; - email: string; + event: Event & { + formattedDateRange: string; }; } const EventCard: React.FC = ({ event }) => { - const startDate = new Date(event.formattedStartTime); - const endDate = new Date(event.formattedEndTime); - - const formattedDate = format(startDate, 'MMMM d, yyyy'); - const formattedTime = `${format(startDate, 'h:mm a')} - ${format(endDate, 'h:mm a')}`; - return (

{event.title}

-

{formattedDate}

-

{formattedTime}

+

{event.formattedDateRange}

{event.description}

Contact: {event.pointOfContact}

); }; -export default EventCard; \ No newline at end of file +export default EventCard; diff --git a/client/src/components/Footer.tsx b/client/src/components/Footer.tsx index d8e28f0..8066117 100644 --- a/client/src/components/Footer.tsx +++ b/client/src/components/Footer.tsx @@ -7,7 +7,7 @@ const Footer: React.FC = () => { email: '', subject: '', message: '', - honeypot: '', // Honeypot field + honeypot: '', }); const [status, setStatus] = useState(''); const [errors, setErrors] = useState>({}); @@ -45,15 +45,13 @@ const Footer: React.FC = () => { return; } - // Check honeypot field if (formData.honeypot) { setStatus('Form submission rejected.'); return; } - // Rate limiting const currentTime = Date.now(); - if (currentTime - lastSubmissionTime < 60000) { // 1 minute cooldown + if (currentTime - lastSubmissionTime < 60000) { setStatus('Please wait a moment before submitting again.'); return; } @@ -115,12 +113,12 @@ const Footer: React.FC = () => {

Quick Links

-
    -
  • Home
  • -
  • Calendar
  • -
  • Bylaws
  • -
  • Sponsors
  • -
  • Meet MCDi Board
  • +
      +
    • Home
    • +
    • Calendar
    • +
    • Bylaws
    • +
    • Sponsors
    • +
    • Meet MCDi Board
@@ -198,4 +196,4 @@ const Footer: React.FC = () => { ); }; -export default Footer; \ No newline at end of file +export default Footer; diff --git a/client/src/components/Header.tsx b/client/src/components/Header.tsx index ccc4b9e..cd47a78 100644 --- a/client/src/components/Header.tsx +++ b/client/src/components/Header.tsx @@ -3,15 +3,17 @@ import { Link } from 'react-router-dom'; import { motion, AnimatePresence } from 'framer-motion'; const Header: React.FC = () => { + const [isMenuOpen, setIsMenuOpen] = useState(false); + return (
- MCDi Logo -

Missoula Council of the Deaf, Inc

+ MCDi Logo +

Missoula Council of the Deaf, Inc

-
+ + {isMenuOpen && ( + +
    +
  • setIsMenuOpen(false)}>Home
  • +
  • setIsMenuOpen(false)}>About MCDi
  • +
  • setIsMenuOpen(false)}>Calendar
  • +
  • setIsMenuOpen(false)}>Join MCDi
  • +
  • setIsMenuOpen(false)} />
  • +
+
+ )} +
); }; @@ -29,15 +57,17 @@ const Header: React.FC = () => { interface NavItemProps { href: string; children: React.ReactNode; + onClick?: () => void; } -const NavItem: React.FC = ({ href, children }) => ( - +const NavItem: React.FC = ({ href, children, onClick }) => ( + {children} ); + const AboutDropdown: React.FC = () => { const [isOpen, setIsOpen] = useState(false); @@ -75,6 +105,7 @@ const AboutDropdown: React.FC = () => {
); }; + const DropdownItem: React.FC = ({ href, children }) => ( @@ -83,15 +114,15 @@ const DropdownItem: React.FC = ({ href, children }) => ( ); -const DonateButton: React.FC = () => ( +const DonateButton: React.FC<{ onClick?: () => void }> = ({ onClick }) => ( - + Donate ); -export default Header; \ No newline at end of file +export default Header; diff --git a/client/src/components/Home.tsx b/client/src/components/Home.tsx index 7ba06ff..f778aa9 100644 --- a/client/src/components/Home.tsx +++ b/client/src/components/Home.tsx @@ -2,15 +2,9 @@ import React, { useState, useEffect } from 'react'; import { motion } from 'framer-motion'; import ImageCarousel from './ImageCarousel'; import EventCard from './EventCard'; -import { parseISO, startOfDay, isSameDay, isAfter, format } from 'date-fns'; +import { parseISO, startOfDay, isSameDay, isAfter } from 'date-fns'; import { events, Event } from '../eventData'; - -const formatTime = (dateString: string, time: string): string => { - const date = parseISO(dateString); - const [hours, minutes] = time.split(':'); - const eventDate = new Date(date.setHours(parseInt(hours, 10), parseInt(minutes, 10))); - return format(eventDate, 'MMMM d, yyyy h:mm a'); -}; +import { formatDateRange } from './Calendar'; const isEventUpcoming = (eventDate: string) => { const today = startOfDay(new Date()); @@ -35,13 +29,14 @@ const Home: React.FC = () => { initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5 }} + className="space-y-8" > -

Welcome to MCDi

+

Welcome to MCDi

-
-
+
+
{ key={index} event={{ ...event, - formattedStartTime: formatTime(event.date, event.startTime), - formattedEndTime: formatTime(event.date, event.endTime) + formattedDateRange: formatDateRange(event.date, event.startTime, event.date, event.endTime) }} /> ))}
-
+
= ({ title, content whileHover={{ scale: 1.03 }} transition={{ duration: 0.2 }} > -

{title}

-

{content}

+

{title}

+

{content}

); diff --git a/client/src/components/Meet-Board.tsx b/client/src/components/Meet-Board.tsx index 218a9ea..5489fea 100644 --- a/client/src/components/Meet-Board.tsx +++ b/client/src/components/Meet-Board.tsx @@ -122,18 +122,13 @@ const MeetBoard: React.FC = () => { animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5 }} > -

Meet MCDi Board

+

Meet MCDi Board

-
- {shuffledMembers.slice(0, 3).map((member, index) => ( +
+ {shuffledMembers.map((member, index) => ( setSelectedMember(member)} /> ))}
-
- {shuffledMembers.slice(3, 5).map((member, index) => ( - setSelectedMember(member)} /> - ))} -
{ initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} - className="absolute bottom-0 left-0 right-0 bg-black bg-opacity-50 p-4" + className="absolute bottom-0 left-0 right-0 bg-black bg-opacity-50 p-2 md:p-4" >
+ )} @@ -63,9 +63,9 @@ const Sponsors: React.FC = () => { animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5 }} > -

Our Sponsors

+

Our Sponsors

-
+
{ description={sponsorInfo["Imagine Nation Brewing"]} onClick={() => setSelectedSponsor("Imagine Nation Brewing")} /> -
-
-
- setSelectedSponsor("Gild Brewing")} - /> -
+ setSelectedSponsor("Gild Brewing")} + />
setSelectedSponsor(null)}> {selectedSponsor && ( <> -

{selectedSponsor}

-

{sponsorInfo[selectedSponsor as keyof typeof sponsorInfo]}

+

{selectedSponsor}

+

{sponsorInfo[selectedSponsor as keyof typeof sponsorInfo]}

)}
@@ -103,4 +99,4 @@ const Sponsors: React.FC = () => { ); }; -export default Sponsors; +export default Sponsors; \ No newline at end of file diff --git a/client/src/index.css b/client/src/index.css index e8160e2..6a7f6e2 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -2,4 +2,108 @@ @import 'tailwindcss/components'; @import 'tailwindcss/utilities'; -/* You can add any custom styles here */ +/* Custom styles */ +html, body { + @apply antialiased; +} + +body { + @apply bg-gray-100; +} + +/* Mobile-first approach */ +.container { + @apply px-4 sm:px-6 lg:px-8; +} + +/* Header styles */ +.header-nav { + @apply flex flex-col sm:flex-row items-center justify-between; +} + +.nav-item { + @apply block py-2 px-4 text-center sm:inline-block sm:py-0; +} + +/* Footer styles */ +.footer-content { + @apply flex flex-col sm:flex-row justify-between items-start sm:items-center; +} + +.footer-section { + @apply w-full sm:w-1/3 mb-6 sm:mb-0; +} + +/* Form styles */ +.form-input { + @apply w-full p-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-[#8C1D40] focus:border-transparent; +} + +/* Button styles */ +.btn { + @apply px-4 py-2 rounded-md transition duration-300 ease-in-out; +} + +.btn-primary { + @apply bg-[#8C1D40] text-white hover:bg-[#6B1631]; +} + +/* Responsive typography */ +h1 { + @apply text-3xl sm:text-4xl font-bold; +} + +h2 { + @apply text-2xl sm:text-3xl font-semibold; +} + +/* Custom scrollbar for webkit browsers */ +::-webkit-scrollbar { + @apply w-2; +} + +::-webkit-scrollbar-track { + @apply bg-gray-200; +} + +::-webkit-scrollbar-thumb { + @apply bg-[#8C1D40] rounded-full; +} + +/* Smooth scrolling for the entire page */ +html { + scroll-behavior: smooth; +} + +/* Improved focus styles for accessibility */ +*:focus { + @apply outline-none ring-2 ring-[#8C1D40] ring-opacity-50; +} + +/* Custom utility classes */ +.text-shadow { + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1); +} + +.hover-grow { + @apply transition-transform duration-300 ease-in-out; +} + +.hover-grow:hover { + @apply transform scale-105; +} + +/* Responsive grid layouts */ +.grid-responsive { + @apply grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6; +} + +/* Custom animations */ +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +.animate-fadeIn { + animation: fadeIn 0.5s ease-in-out; +} diff --git a/client/tailwind.config.js b/client/tailwind.config.js index 9cae415..e9f252e 100644 --- a/client/tailwind.config.js +++ b/client/tailwind.config.js @@ -1,10 +1,32 @@ module.exports = { - content: [ - "./src/**/*.{js,jsx,ts,tsx}", - "./public/index.html", - ], - theme: { - extend: {}, + content: [ + "./src/**/*.{js,jsx,ts,tsx}", + "./public/index.html", + ], + theme: { + extend: { + colors: { + 'mcdi-maroon': '#8C1D40', + 'mcdi-gold': '#FFC627', + }, + fontFamily: { + 'sans': ['Roboto', 'Arial', 'sans-serif'], + 'serif': ['Merriweather', 'Georgia', 'serif'], + }, + screens: { + 'xs': '475px', + }, + spacing: { + '128': '32rem', + '144': '36rem', + }, + minHeight: { + '1/2': '50vh', + }, }, - plugins: [], - } \ No newline at end of file + }, + plugins: [ + require('@tailwindcss/forms'), + require('@tailwindcss/typography'), + ], +} \ No newline at end of file