From e7bcbb249098dc91829daa9214b0b04075610dc3 Mon Sep 17 00:00:00 2001 From: aidenm1 Date: Wed, 6 Nov 2024 18:37:26 -0800 Subject: [PATCH] implemented dynamic routing for each event and created event details page --- api/supabase/queries/events.ts | 40 +++ api/supabase/queries/facility.ts | 30 ++ api/supabase/queries/volunteers.ts | 28 ++ app/events/[event_id]/page.tsx | 277 ++++++++++++++++++ app/events/[event_id]/styles.ts | 124 ++++++++ app/events/page.tsx | 14 +- app/events/{page.style.ts => styles.ts} | 6 +- components/MyEventCard/MyEventCard.tsx | 63 ++-- .../MyEventCard/{style.ts => styles.ts} | 3 +- public/images/black_loc_pin.svg | 6 + public/images/calendar_icon.svg | 5 + public/images/facility_contact_icon.svg | 10 + .../images/{gg_pin.svg => gray_loc_pin.svg} | 0 public/images/host_icon.svg | 3 + public/images/producer_icon.svg | 3 + public/images/volunteer_performer_icon.svg | 3 + public/images/white_back.svg | 6 + utils/formatTime.ts | 54 ++++ 18 files changed, 627 insertions(+), 48 deletions(-) create mode 100644 api/supabase/queries/facility.ts create mode 100644 api/supabase/queries/volunteers.ts create mode 100644 app/events/[event_id]/page.tsx create mode 100644 app/events/[event_id]/styles.ts rename app/events/{page.style.ts => styles.ts} (88%) rename components/MyEventCard/{style.ts => styles.ts} (94%) create mode 100644 public/images/black_loc_pin.svg create mode 100644 public/images/calendar_icon.svg create mode 100644 public/images/facility_contact_icon.svg rename public/images/{gg_pin.svg => gray_loc_pin.svg} (100%) create mode 100644 public/images/host_icon.svg create mode 100644 public/images/producer_icon.svg create mode 100644 public/images/volunteer_performer_icon.svg create mode 100644 public/images/white_back.svg create mode 100644 utils/formatTime.ts diff --git a/api/supabase/queries/events.ts b/api/supabase/queries/events.ts index 6b8baf9..f96c4a0 100644 --- a/api/supabase/queries/events.ts +++ b/api/supabase/queries/events.ts @@ -53,3 +53,43 @@ export async function fetchAllActiveEvents() { return data; } + +export async function fetchEventByID(event_id: UUID) { + const { data, error } = await supabase + .from('events') + .select('*') + .eq('event_id', event_id) + .single(); + + if (error) { + throw new Error(error.message); + } + + return data; +} + +export async function fetchEventHost(event_id: UUID) { + const { data, error } = await supabase + .from('event_signups') + .select('*') + .eq('event_id', event_id) + .eq('role', 'HOST') + .eq('is_accepted', true) + .single(); + + if (error) { + throw new Error(error.message); + } + + const { data: host, error: hosterror } = await supabase + .from('volunteers') + .select('*') + .eq('user_id', data.user_id) + .single(); + + if (hosterror) { + throw new Error(hosterror.message); + } + + return host; +} diff --git a/api/supabase/queries/facility.ts b/api/supabase/queries/facility.ts new file mode 100644 index 0000000..c6cc8be --- /dev/null +++ b/api/supabase/queries/facility.ts @@ -0,0 +1,30 @@ +import { UUID } from 'crypto'; +import supabase from '../createClient'; + +export async function fetchFacilityByID(facility_id: UUID) { + const { data, error } = await supabase + .from('facilities') + .select('*') + .eq('facility_id', facility_id) + .single(); + + if (error) { + throw new Error(error.message); + } + + return data; +} + +export async function fetchFacilityContactByID(facility_id: UUID) { + const { data, error } = await supabase + .from('facility_contacts') + .select('*') + .eq('facility_id', facility_id) + .single(); + + if (error) { + throw new Error(error.message); + } + + return data; +} diff --git a/api/supabase/queries/volunteers.ts b/api/supabase/queries/volunteers.ts new file mode 100644 index 0000000..4228f69 --- /dev/null +++ b/api/supabase/queries/volunteers.ts @@ -0,0 +1,28 @@ +import { UUID } from 'crypto'; +import supabase from '../createClient'; + +export async function fetchPerformer(event_id: UUID) { + const { data, error } = await supabase + .from('event_signups') + .select('*') + .eq('event_id', event_id) + .eq('role', 'PERFORMER') + .eq('is_accepted', true) + .single(); + + if (error) { + throw new Error(error.message); + } + + const { data: performer, error: performererror } = await supabase + .from('volunteers') + .select('*') + .eq('user_id', data.user_id) + .single(); + + if (performererror) { + throw new Error(performererror.message); + } + + return performer; +} diff --git a/app/events/[event_id]/page.tsx b/app/events/[event_id]/page.tsx new file mode 100644 index 0000000..67cb16f --- /dev/null +++ b/app/events/[event_id]/page.tsx @@ -0,0 +1,277 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import Link from 'next/link'; +import { UUID } from 'crypto'; +import { fetchEventByID, fetchEventHost } from '@/api/supabase/queries/events'; +import { + fetchFacilityByID, + fetchFacilityContactByID, +} from '@/api/supabase/queries/facility'; +import { fetchPerformer } from '@/api/supabase/queries/volunteers'; +import LocPin from '@/public/images/black_loc_pin.svg'; +import BPLogo from '@/public/images/bp-logo.png'; +import Calendar from '@/public/images/calendar_icon.svg'; +import FacilityContactPin from '@/public/images/facility_contact_icon.svg'; +import HostPin from '@/public/images/host_icon.svg'; +import ProducerIcon from '@/public/images/producer_icon.svg'; +import PerformerPin from '@/public/images/volunteer_performer_icon.svg'; +import WhiteBack from '@/public/images/white_back.svg'; +import COLORS from '@/styles/colors'; +import { + Event, + Facilities, + FacilityContacts, + Volunteers, +} from '@/types/schema'; +import formatTime from '@/utils/formatTime'; +import * as styles from './styles'; + +export default function EventDisplay({ + params, +}: { + params: { event_id: UUID }; +}) { + const [event, setEvent] = useState(); + const [facility, setFacility] = useState(); + const [facility_contact, setFacilityContact] = useState(); + const [host_name, setHostName] = useState(); + const [host_phone_number, setHostPhoneNumber] = useState(); + const [performer, setPerformer] = useState(); + + useEffect(() => { + const getEvent = async () => { + const fetchedEvent: Event = await fetchEventByID(params.event_id); + setEvent(fetchedEvent); + const fetchedFacility: Facilities = await fetchFacilityByID( + fetchedEvent.facility_id, + ); + setFacility(fetchedFacility); + const fetchedFacilityContact: FacilityContacts = + await fetchFacilityContactByID(fetchedEvent.facility_id); + setFacilityContact(fetchedFacilityContact); + + if (fetchedEvent.needs_host) { + const host: Volunteers = await fetchEventHost(params.event_id); + setHostName(`${host.first_name} ${host.last_name}`); + setHostPhoneNumber(host.phone_number); + } else { + setHostName(fetchedFacility.host_name); + setHostPhoneNumber(fetchedFacility.host_contact); + } + + const fetchedPerformer: Volunteers = await fetchPerformer( + params.event_id, + ); + setPerformer(fetchedPerformer); + }; + getEvent(); + }, [params.event_id]); + + // Render once the event is fetched + if (!event || !facility || !facility_contact || !performer) { + return

; + } + + const time = formatTime( + new Date(event.start_date_time), + new Date(event.end_date_time), + true, + ); + const location = facility.street_address_1 + ', ' + facility.city; + + return ( + + + + + + + + + + + Event Title Here + + + + + {time} + + + + + + + {facility.name} + + + {location} + + + + + + Notes + +
+ + Notes from the facility + + + Facility notes go here + +
+
+ + Notes from the producer + + + {event.notes} + +
+
+ + + Contacts + + + + + + {facility_contact.first_name} {facility_contact.last_name} + + + Facility Contact + + + {facility_contact.phone_number} + + + + + + + + {host_name} + + + Volunteer Host + + {/* Should this be fixed, or should it changed based on needs_host? */} + + {host_phone_number} + + + + + + + + {performer.first_name} {performer.last_name} + + + Volunteer Performer + + {/* Should this be fixed, or should it changed based on needs_host? */} + + {performer.phone_number} + + + + + + + + Producer Name + + + Show Producer + + {/* Should this be fixed, or should it changed based on needs_host? */} + + Producer Number + + + + +
+
+ ); +} diff --git a/app/events/[event_id]/styles.ts b/app/events/[event_id]/styles.ts new file mode 100644 index 0000000..9c99eef --- /dev/null +++ b/app/events/[event_id]/styles.ts @@ -0,0 +1,124 @@ +'use client'; + +import NextImage from 'next/image'; +import styled from 'styled-components'; +import COLORS from '@/styles/colors'; +import { H4, H5, P, SMALL } from '@/styles/text'; + +export const BackImage = styled(NextImage)` + position: absolute; + top: 0; + left: 0; + width: 1.5rem; + height: 1.5rem; + margin: 1rem; +`; + +export const ImageWrapper = styled.div` + position: relative; + width: 100%; + height: auto; + display: flex; +`; + +export const EventImage = styled(NextImage)` + width: 100%; + height: auto; + max-height: 11.5rem; + object-fit: cover; +`; + +export const GradientOverlay = styled.div` + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient( + 0deg, + rgba(0, 0, 0, 0.35) 0%, + rgba(0, 0, 0, 0.35) 100% + ); +`; + +export const Page = styled.div` + background-color: ${COLORS.gray1}; + flex-direction: column; + min-width: 100%; + min-height: 100svh; + overflow: hidden; + margin-bottom: 3.75rem; +`; + +export const Container = styled.div` + padding-left: 2rem; + padding-right: 2rem; + display: flex; + flex-direction: column; +`; + +export const EventText = styled(H4)` + font-style: normal; + line-height: normal; + margin-top: 1.5rem; + margin-bottom: 0.5rem; +`; + +export const CalLocPin = styled(NextImage)` + width: 1.125rem; + height: auto; + margin-right: 0.5rem; + margin-bottom: 0.5rem; +`; + +export const DateLocation = styled.div` + display: flex; + align-items: flex-start; + justify-content: left; +`; + +export const ParaText = styled(P)` + font-style: normal; + line-height: normal; + align-items: flex-end; + justify-content: left; +`; + +export const LocationDetails = styled.div` + display: flex; + flex-direction: column; +`; + +export const SubHeadingText = styled(H5)` + padding-top: 2.5rem; +`; + +export const AllNotesAndContactsContainer = styled.div` + display: flex; + flex-direction: column; + gap: 1.75rem; +`; +export const ContactPins = styled(NextImage)` + width: 2rem; + height: auto; +`; + +export const ContactContainer = styled.div` + display: flex; + align-items: center; + justify-content: left; + gap: 0.75rem; +`; + +export const ContactDetails = styled.div` + display: flex; + flex-direction: column; +`; + +export const ContactTypeText = styled(SMALL)` + font-style: italic; +`; + +export const PhoneNumberText = styled(P)` + padding-top: 0.125rem; +`; diff --git a/app/events/page.tsx b/app/events/page.tsx index a975d30..4da40d0 100644 --- a/app/events/page.tsx +++ b/app/events/page.tsx @@ -1,11 +1,12 @@ 'use client'; import React, { useEffect, useState } from 'react'; +import Link from 'next/link'; import { fetchAcceptedEventsByVolunteer } from '@/api/supabase/queries/events'; import MyEventCard from '@/components/MyEventCard/MyEventCard'; import Menu from '@/public/images/ic_baseline-menu.svg'; import { Event } from '@/types/schema'; -import * as styles from './page.style'; +import * as styles from './styles'; type GroupedEvents = { [monthYear: string]: Event[]; // Each key is a "Month Year" string, and the value is an array of Events @@ -16,6 +17,7 @@ export default function EventPage() { useEffect(() => { fetchAcceptedEventsByVolunteer('11d219d9-bf05-4a06-a23e-89fd566c7a04').then( + //placeholder user id eventsData => { setData(eventsData ?? []); }, @@ -59,7 +61,7 @@ export default function EventPage() { return ( - + Upcoming Events @@ -70,7 +72,13 @@ export default function EventPage() { {month} {events.map(event => ( - + + + ))} ))} diff --git a/app/events/page.style.ts b/app/events/styles.ts similarity index 88% rename from app/events/page.style.ts rename to app/events/styles.ts index 936d0bf..c4a89f8 100644 --- a/app/events/page.style.ts +++ b/app/events/styles.ts @@ -5,7 +5,7 @@ import styled from 'styled-components'; import COLORS from '@/styles/colors'; import { H3, H6 } from '@/styles/text'; -export const Image = styled(NextImage)` +export const MenuImage = styled(NextImage)` width: 1.5rem; height: 1.5rem; margin: 1rem; @@ -20,8 +20,8 @@ export const Page = styled.main` `; export const AllEventsHolder = styled.main` - padding-left: 2rem; - padding-right: 2rem; + padding-left: 1.5rem; + padding-right: 1.5rem; display: flex; flex-direction: column; gap: 1.5rem; diff --git a/components/MyEventCard/MyEventCard.tsx b/components/MyEventCard/MyEventCard.tsx index 6811c4d..26f2205 100644 --- a/components/MyEventCard/MyEventCard.tsx +++ b/components/MyEventCard/MyEventCard.tsx @@ -1,53 +1,34 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; +import { fetchFacilityByID } from '@/api/supabase/queries/facility'; import BPLogo from '@/public/images/bp-logo.png'; -import LocPin from '@/public/images/gg_pin.svg'; +import LocPin from '@/public/images/gray_loc_pin.svg'; import COLORS from '@/styles/colors'; -import { Event } from '@/types/schema'; -import * as styles from './style'; +import { Event, Facilities } from '@/types/schema'; +import formatTime from '@/utils/formatTime'; +import * as styles from './styles'; export default function MyEventCard(eventData: Event) { - const eventStart = new Date(eventData.start_date_time); - const eventEnd = new Date(eventData.end_date_time); + const [facility, setFacility] = useState(); - // function to remove 00 from time if time is on the hour, ex: 4:00 PM -> 4 PM - const formatTime = (date: Date) => { - const minutes = date.getMinutes(); + useEffect(() => { + fetchFacilityByID(eventData.facility_id).then(facilityData => { + setFacility(facilityData); + }); + }, [eventData.facility_id]); - return minutes === 0 - ? date.toLocaleTimeString([], { hour: 'numeric', hour12: true }) - : date.toLocaleTimeString([], { - hour: 'numeric', - minute: '2-digit', - hour12: true, - }); - }; - - const startTime = formatTime(eventStart); - const endTime = formatTime(eventEnd); - - const monthNames = [ - 'Jan', - 'Feb', - 'Mar', - 'Apr', - 'May', - 'Jun', - 'Jul', - 'Aug', - 'Sep', - 'Oct', - 'Nov', - 'Dec', - ]; - const monthText = monthNames[eventStart.getMonth()]; + const formattedTime = formatTime( + new Date(eventData.start_date_time), + new Date(eventData.end_date_time), + false, + ); return ( - +
- {monthText} {eventStart.getDate()}, {startTime} - {endTime} + {formattedTime} - - placeholder + + {facility + ? `${facility.street_address_1}, ${facility.city}` + : 'Fetching location...'}
diff --git a/components/MyEventCard/style.ts b/components/MyEventCard/styles.ts similarity index 94% rename from components/MyEventCard/style.ts rename to components/MyEventCard/styles.ts index 1bc0674..3189194 100644 --- a/components/MyEventCard/style.ts +++ b/components/MyEventCard/styles.ts @@ -5,8 +5,7 @@ import styled from 'styled-components'; import { P, SMALLER } from '@/styles/text'; import COLORS from '../../styles/colors'; -export const BPImage = styled(NextImage)` - layout: responsive; +export const EventImage = styled(NextImage)` width: 20%; height: 90%; `; diff --git a/public/images/black_loc_pin.svg b/public/images/black_loc_pin.svg new file mode 100644 index 0000000..4e9cd42 --- /dev/null +++ b/public/images/black_loc_pin.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/images/calendar_icon.svg b/public/images/calendar_icon.svg new file mode 100644 index 0000000..78e5e27 --- /dev/null +++ b/public/images/calendar_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/images/facility_contact_icon.svg b/public/images/facility_contact_icon.svg new file mode 100644 index 0000000..3dc7162 --- /dev/null +++ b/public/images/facility_contact_icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/images/gg_pin.svg b/public/images/gray_loc_pin.svg similarity index 100% rename from public/images/gg_pin.svg rename to public/images/gray_loc_pin.svg diff --git a/public/images/host_icon.svg b/public/images/host_icon.svg new file mode 100644 index 0000000..20030a6 --- /dev/null +++ b/public/images/host_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/images/producer_icon.svg b/public/images/producer_icon.svg new file mode 100644 index 0000000..6af49f6 --- /dev/null +++ b/public/images/producer_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/images/volunteer_performer_icon.svg b/public/images/volunteer_performer_icon.svg new file mode 100644 index 0000000..b581925 --- /dev/null +++ b/public/images/volunteer_performer_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/images/white_back.svg b/public/images/white_back.svg new file mode 100644 index 0000000..c43784d --- /dev/null +++ b/public/images/white_back.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/utils/formatTime.ts b/utils/formatTime.ts new file mode 100644 index 0000000..87118ab --- /dev/null +++ b/utils/formatTime.ts @@ -0,0 +1,54 @@ +export default function formatTime( + eventStart: Date, + eventEnd: Date, + long: boolean, +) { + const formatMinutes = (date: Date) => { + const minutes = date.getMinutes(); + return minutes === 0 + ? date.toLocaleTimeString([], { hour: 'numeric', hour12: true }) + : date.toLocaleTimeString([], { + hour: 'numeric', + minute: '2-digit', + hour12: true, + }); + }; + + const startTime = formatMinutes(eventStart); + const endTime = formatMinutes(eventEnd); + + // Use a plain variable for month names instead of React state + const monthNames = long + ? [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', + ] + : [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec', + ]; + + const monthText = monthNames[eventStart.getMonth()]; + + return `${monthText} ${eventStart.getDate()}, ${startTime} - ${endTime}`; +}