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);