Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions src/components/VolunteerOpportunity.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import SimpleLineIcons from "@expo/vector-icons/SimpleLineIcons";
import { Image } from "expo-image";
import { Platform, StyleSheet, Text, View } from "react-native";
import { TouchableOpacity } from "react-native-gesture-handler";
import Markdown from "react-native-markdown-display";

export default function VolunteerOpportunity({
navigation,
Expand Down Expand Up @@ -80,16 +81,23 @@ export default function VolunteerOpportunity({
{date}
</Text>
{/* Location info with icon */}
<Text style={styles.info} selectable>
<View style={{ flexDirection: "row", alignItems: "center" }}>
<SimpleLineIcons
name="location-pin"
size={10}
color="black"
style={styles.icon}
/>
{" "}
{location}
</Text>
<Markdown
style={{
body: { fontSize: 10, color: "#555" },
paragraph: { marginTop: 0, marginBottom: 0 },
link: { color: "#555" },
}}
Comment thread
Pramad712 marked this conversation as resolved.
>
{location}
</Markdown>
</View>
</View>
</View>
{/* Right-side indicator chevron */}
Expand Down
27 changes: 15 additions & 12 deletions src/screens/VolunteerFormScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,19 @@ import {
Text,
View,
} from "react-native";
import Markdown from "react-native-markdown-display";
import WebView from "react-native-webview";
import Fuse from "fuse.js";
import ErrorBoundary from "react-native-error-boundary";

import NextButton from "../components/NextButton";
import PersistScrollView from "../components/PersistScrollView";

import { alertError, sendErrorEmail, openInMaps } from "../utils";
import { openURL, sendErrorEmail } from "../utils";
import DanceClub from "../utils/forms/DanceClub";
import LibraryMusicHour from "../utils/forms/LibraryMusicHour";
import MusicByTheTracks from "../utils/forms/MusicByTheTracks";
import RequestConcert from "../utils/forms/RequestConcert";
import colors from "../constants/colors";
import formIDs from "../constants/formIDs";

// Factory: choose form class by event title using fuzzy matching
Expand Down Expand Up @@ -160,14 +160,21 @@ export default function VolunteerFormScreen({ navigation, route }) {
</Text>
)}
{location == null ? null : (
<Pressable onPress={() => openInMaps(location)}>
<Text
style={[styles.headerText, styles.locationText]}
selectable={true}
<View style={{ alignItems: "center" }}>
<Markdown
onLinkPress={(url) => {
openURL(url);
Comment thread
Pramad712 marked this conversation as resolved.
return false;
}}
Comment thread
Pramad712 marked this conversation as resolved.
style={{
body: {
fontSize: 18,
},
}}
>
{location}
</Text>
</Pressable>
</Markdown>
</View>
)}
</View>
{/* Render each question component from form */}
Expand Down Expand Up @@ -262,8 +269,4 @@ const styles = StyleSheet.create({
justifyContent: "flex-end",
marginBottom: 50,
},
locationText: {
textDecorationLine: "underline",
color: colors.primary,
},
});
38 changes: 16 additions & 22 deletions src/screens/VolunteerOpportunityScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,15 @@ 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";
import { openURL } from "../utils";

export default function VolunteerOpportunityScreen({ route, navigation }) {
// Destructure parameters passed via navigation
Expand Down Expand Up @@ -95,14 +88,19 @@ export default function VolunteerOpportunityScreen({ route, navigation }) {
size={20}
color={colors.black}
/>
<Pressable onPress={() => openInMaps(location)}>
<Text
style={[styles.detailsText, styles.locationText]}
selectable
>
{location}
</Text>
</Pressable>
<Markdown
onLinkPress={(url) => {
openURL(url);
return false;
}}
Comment thread
Pramad712 marked this conversation as resolved.
style={{
body: {
fontSize: 18,
},
}}
>
{location}
</Markdown>
</View>
</View>
{/* Optional description section */}
Expand All @@ -111,7 +109,7 @@ export default function VolunteerOpportunityScreen({ route, navigation }) {
<Heading>About</Heading>
<Markdown
onLinkPress={(url) => {
Linking.openURL(url);
openURL(url);
return false;
}}
style={{
Expand Down Expand Up @@ -269,8 +267,4 @@ const styles = StyleSheet.create({
detailsText: {
fontSize: 18,
},
locationText: {
textDecorationLine: "underline",
color: colors.primary,
},
});
49 changes: 1 addition & 48 deletions src/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@
* index.js
* Shared utility functions:
* - alertError: standardized error alert + EmailJS error report
* - openURL / maybeOpenURL: external link handling with app store fallback
* - openURL: external link handling with app store fallback
* - request: retry wrapper with exponential backoff for network calls
Comment thread
Pramad712 marked this conversation as resolved.
* - strToDate / formatDate: Google Sheets date parsing and formatting
* - Question: form question helper class
* - emptyQuestionState: hook for question state
* - isAtLeast, isNotEmpty, isExactly: basic validation predicates
* - isValidEmail, isValidPhoneNumber: validator.js-backed field validators
* - openInMaps: launch maps app for a location
*/

import Constants from "expo-constants";
Expand Down Expand Up @@ -79,30 +78,6 @@ export function openURL(url) {
});
}

/**
* Try to open URL, fallback to app store if scheme fails.
* @param {string} url
* @param {string} appName
* @param {string} appStoreID
* @param {string} playStoreID
*/
export function maybeOpenURL(url, appName, appStoreID, playStoreID) {
Linking.openURL(url).catch((error) => {
if (error.code == "EUNSPECIFIED") {
if (Platform.OS == "ios") {
openURL(`https://apps.apple.com/us/app/${appName}/id${appStoreID}`);
} else {
openURL(`https://play.google.com/store/apps/details?id=${playStoreID}`);
}
} else {
Alert.alert(
`Unable to open URL`,
`Your device does not support opening ${url} from this app. Please copy and paste the URL into your browser.`,
);
}
});
}

/**
* Utility delay function for retry backoff.
*/
Expand Down Expand Up @@ -215,25 +190,3 @@ export const isAtLeast = (value, len) =>
export const isNotEmpty = (value) => isAtLeast(value, 1);
export const isExactly = (value, len) =>
!isAtLeast(value, len + 1) && isAtLeast(value, len);

/**
* Launch maps application or fallback to web URL for a location.
* @param {string} location
*/
export function openInMaps(location) {
const encodedLocation = encodeURIComponent(location);
const url = Platform.select({
ios: `maps://maps.apple.com/?q=${encodedLocation}`,
android: `https://www.google.com/maps/search/?api=1&query=${encodedLocation}`,
});
Linking.canOpenURL(url).then((supported) => {
if (supported) {
Linking.openURL(url);
} else {
// Fallback to Google Maps web URL
Linking.openURL(
`https://www.google.com/maps/search/?api=1&query=${encodedLocation}`,
);
}
});
}
Loading