diff --git a/src/components/CarouselSection.js b/src/components/CarouselSection.js index d1d7345..6cc1116 100644 --- a/src/components/CarouselSection.js +++ b/src/components/CarouselSection.js @@ -37,6 +37,7 @@ function CarouselSection({ navigation, data, onRefresh }) { location={event.Location} date={formatDate(event.Date)} image={event.Image} + posters={event.Posters} description={event.Description ?? ""} tags={ event.Tags == null diff --git a/src/components/PostersButton.js b/src/components/PostersButton.js new file mode 100644 index 0000000..7165e1b --- /dev/null +++ b/src/components/PostersButton.js @@ -0,0 +1,168 @@ +import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons"; +import { Image } from "expo-image"; +import { useRef, useState } from "react"; +import { + Dimensions, + FlatList, + Modal, + Pressable, + StyleSheet, + Text, + View, +} from "react-native"; + +import colors from "../constants/colors"; + +const { width, height } = Dimensions.get("window"); + +export default function PostersButton({ posters }) { + const [showGallery, setShowGallery] = useState(false); + const [currentIndex, setCurrentIndex] = useState(0); + const flatListRef = useRef(null); + + const images = Array.from(new Set(posters)); + const count = images.length; + const looped = count > 0 ? [...images, ...images, ...images] : []; + + function openGallery() { + setCurrentIndex(count); + setShowGallery(true); + setTimeout(() => { + flatListRef.current?.scrollToIndex({ index: count, animated: false }); + }, 0); + } + + function onMomentumScrollEnd(e) { + const idx = Math.round(e.nativeEvent.contentOffset.x / width); + let normalized = idx; + + if (idx < count) { + normalized = idx + count; + flatListRef.current?.scrollToIndex({ + index: normalized, + animated: false, + }); + } else if (idx >= count * 2) { + normalized = idx - count; + flatListRef.current?.scrollToIndex({ + index: normalized, + animated: false, + }); + } + + setCurrentIndex(normalized); + } + + if (count === 0) return null; + + return ( + + + + Show Posters & Programs + + + setShowGallery(false)} + > + + setShowGallery(false)} + > + + + + String(i)} + getItemLayout={(_, i) => ({ + length: width, + offset: width * i, + index: i, + })} + initialScrollIndex={count} + onMomentumScrollEnd={onMomentumScrollEnd} + renderItem={({ item }) => ( + + + + )} + /> + + + {(currentIndex % count) + 1} / {count} + + + + + ); +} + +const styles = StyleSheet.create({ + button: { + flexDirection: "row", + alignItems: "center", + gap: 15, + paddingVertical: 10, + paddingHorizontal: 15, + borderRadius: 15, + borderWidth: 1, + borderColor: colors.primary, + alignSelf: "center", + marginTop: 20, + }, + buttonText: { + fontSize: 22, + color: colors.primary, + fontWeight: "500", + }, + modalContainer: { + flex: 1, + backgroundColor: "black", + justifyContent: "center", + }, + closeButton: { + position: "absolute", + top: 50, + right: 20, + zIndex: 10, + }, + imagePage: { + width, + height, + justifyContent: "center", + alignItems: "center", + paddingTop: 50, + paddingBottom: 50, + paddingLeft: 50, + paddingRight: 50, + }, + image: { + width: "100%", + height: "100%", + }, + footer: { + color: "white", + fontSize: 16, + textAlign: "center", + position: "absolute", + bottom: 30, + width: "100%", + }, +}); diff --git a/src/components/VolunteerOpportunity.js b/src/components/VolunteerOpportunity.js index cb76e82..2f500b5 100644 --- a/src/components/VolunteerOpportunity.js +++ b/src/components/VolunteerOpportunity.js @@ -29,6 +29,7 @@ export default function VolunteerOpportunity({ location, date, image, + posters, description, tags, formURL, @@ -46,6 +47,7 @@ export default function VolunteerOpportunity({ location, date, image, + posters, description, tags, formURL, diff --git a/src/screens/HomeScreen.js b/src/screens/HomeScreen.js index f7e7520..2547caa 100644 --- a/src/screens/HomeScreen.js +++ b/src/screens/HomeScreen.js @@ -133,6 +133,12 @@ export default function HomeScreen({ navigation, route }) { opportunity.Location ??= "Unknown Location"; opportunity.Date = strToDate(opportunity.Date) ?? new Date(0); + opportunity.Posters = opportunity.Posters + ? opportunity.Posters.split(",") + .map((s) => s.trim()) + .filter(Boolean) + : []; + const event_midnight = new Date(opportunity.Date); event_midnight.setHours(23, 59, 59, 999); diff --git a/src/screens/VolunteerFormScreen.js b/src/screens/VolunteerFormScreen.js index b1ea13b..f5d6f4a 100644 --- a/src/screens/VolunteerFormScreen.js +++ b/src/screens/VolunteerFormScreen.js @@ -181,12 +181,6 @@ export default function VolunteerFormScreen({ navigation, route }) { {/* Render each question component from form */} - {(() => { - const Bad = () => { - throw new Error("Test error"); - }; - return ; - })()} {form .questions() .filter((question) => question?.isVisible()) diff --git a/src/screens/VolunteerOpportunityScreen.js b/src/screens/VolunteerOpportunityScreen.js index 53171f2..d7ad16a 100644 --- a/src/screens/VolunteerOpportunityScreen.js +++ b/src/screens/VolunteerOpportunityScreen.js @@ -1,10 +1,12 @@ -/** +/* VolunteerOpportunityScreen.js * VolunteerOpportunityScreen.js * Detailed view of a volunteer event. * - Displays banner image with gradient overlay and title * - Shows event date and location (tap to open in maps) * - Optionally displays description and tags * - Provides Sign Up button (navigates to form or Google Forms URL) + * - Provides Show Posters & Programs button to display all show programs and posters associated with the event + * - Show Posters & Programs are clickable and pop-up when clicked */ import Markdown from "react-native-markdown-display"; @@ -12,18 +14,12 @@ import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons"; import SimpleLineIcons from "@expo/vector-icons/SimpleLineIcons"; import { ImageBackground } from "expo-image"; import { LinearGradient } from "expo-linear-gradient"; -import { - Linking, - Pressable, - SafeAreaView, - StyleSheet, - Text, - View, -} from "react-native"; +import { Pressable, SafeAreaView, StyleSheet, Text, View } from "react-native"; import Heading from "../components/Heading"; import NextButton from "../components/NextButton"; import PersistScrollView from "../components/PersistScrollView"; +import PostersButton from "../components/PostersButton"; import Tag from "../components/Tag"; import colors from "../constants/colors"; import { openInMaps } from "../utils"; @@ -35,6 +31,7 @@ export default function VolunteerOpportunityScreen({ route, navigation }) { location, date, image, + posters, description, tags, formURL, @@ -53,7 +50,7 @@ export default function VolunteerOpportunityScreen({ route, navigation }) { {/* Banner with background image and gradient overlay */} {/* Overlay title text at bottom-left of banner */} @@ -71,7 +68,6 @@ export default function VolunteerOpportunityScreen({ route, navigation }) { - {/* Scrollable content area */} @@ -103,7 +99,6 @@ export default function VolunteerOpportunityScreen({ route, navigation }) { - {/* Optional description section */} {typeof description === "string" && description.trim() !== "" && ( @@ -151,7 +146,9 @@ export default function VolunteerOpportunityScreen({ route, navigation }) { {tagsIcons} )} - {/* Sign Up button with warning if already submitted */} + + + {isSubmitted && ( diff --git a/src/utils/index.js b/src/utils/index.js index 1ea6b84..328810c 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -49,8 +49,6 @@ export async function sendErrorEmail(error) { publicKey: process.env.EXPO_PUBLIC_EMAIL_PUBLIC_KEY, }, ); - - console.log("SUCCESS!"); } catch (err) { if (err instanceof EmailJSResponseStatus) { console.log("EmailJS Request Failed...", err);