From ec6056e198f1eee3370658127ca53f4c29fad008 Mon Sep 17 00:00:00 2001 From: inunice Date: Mon, 5 May 2025 09:47:38 +0800 Subject: [PATCH 01/17] fix: search party bug --- app/src/contexts/TripSearchContext.tsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/app/src/contexts/TripSearchContext.tsx b/app/src/contexts/TripSearchContext.tsx index e1526c7..fe47510 100644 --- a/app/src/contexts/TripSearchContext.tsx +++ b/app/src/contexts/TripSearchContext.tsx @@ -58,6 +58,7 @@ export function TripSearchProvider({ children }: { children: ReactNode }) { { timeToLeave, sortBy, transportModes }: FilterState, fullTrips?: TripSearch[], ) => { + console.debug("[TripSearch] applyFilters ⇢", sortBy); const currTrip = fullTrips ?? suggestedTrips; const filtered = currTrip @@ -68,7 +69,14 @@ export function TripSearchProvider({ children }: { children: ReactNode }) { ) .sort(getSortFunction(sortBy)); - setFilters({ timeToLeave, sortBy, transportModes }); + // Only update global filters state if array is changed + if ( + timeToLeave !== filters.timeToLeave || + sortBy !== filters.sortBy || + !arrayEqual(transportModes, filters.transportModes) + ) { + setFilters({ timeToLeave, sortBy, transportModes }); + } setFilteredTrips(filtered); }; @@ -93,7 +101,10 @@ export const useTripSearch = (): TripSearchContextType => { return context; }; -// =================== Helper Functions =================== +// Helper + +const arrayEqual = (a: any[], b: any[]) => + a.length === b.length && a.every((item, idx) => item === b[idx]); // Sorting function const getSortFunction = (sortBy: string) => { From c488a41f5cfc2ad0eae12aec316c67d36f060a96 Mon Sep 17 00:00:00 2001 From: inunice Date: Mon, 5 May 2025 10:10:59 +0800 Subject: [PATCH 02/17] fix: party bug (actual) --- app/src/contexts/TripSearchContext.tsx | 46 +++++++++++--------------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/app/src/contexts/TripSearchContext.tsx b/app/src/contexts/TripSearchContext.tsx index fe47510..9074d0a 100644 --- a/app/src/contexts/TripSearchContext.tsx +++ b/app/src/contexts/TripSearchContext.tsx @@ -1,4 +1,4 @@ -import React, { createContext, useState, ReactNode } from "react"; +import React, { createContext, useState, useCallback, ReactNode } from "react"; import { getDirections, paraphraseStep } from "@services/mapbox-service"; import { isNearLocation } from "@utils/map-utils"; @@ -54,31 +54,23 @@ export function TripSearchProvider({ children }: { children: ReactNode }) { } }; - const applyFilters = ( - { timeToLeave, sortBy, transportModes }: FilterState, - fullTrips?: TripSearch[], - ) => { - console.debug("[TripSearch] applyFilters ⇢", sortBy); - const currTrip = fullTrips ?? suggestedTrips; - - const filtered = currTrip - .filter((trip) => - trip.segments.every( - ({ segmentMode }) => segmentMode === "Walk" || transportModes.includes(segmentMode), - ), - ) - .sort(getSortFunction(sortBy)); - - // Only update global filters state if array is changed - if ( - timeToLeave !== filters.timeToLeave || - sortBy !== filters.sortBy || - !arrayEqual(transportModes, filters.transportModes) - ) { - setFilters({ timeToLeave, sortBy, transportModes }); - } - setFilteredTrips(filtered); - }; + const applyFilters = useCallback( + ({ timeToLeave, sortBy, transportModes }: FilterState, fullTrips?: TripSearch[]) => { + console.debug("[TripSearch] applyFilters ⇢", sortBy); + const currTrip = fullTrips ?? suggestedTrips; + + const filtered = [...currTrip] + .filter((trip) => + trip.segments.every( + ({ segmentMode }) => segmentMode === "Walk" || transportModes.includes(segmentMode), + ), + ) + .sort(getSortFunction(sortBy)); + + setFilteredTrips(filtered); + }, + [suggestedTrips, filters], + ); const value = { tripEndpoints, @@ -106,6 +98,8 @@ export const useTripSearch = (): TripSearchContextType => { const arrayEqual = (a: any[], b: any[]) => a.length === b.length && a.every((item, idx) => item === b[idx]); +const dateEqual = (a: Date, b: Date) => a.getTime() === b.getTime(); + // Sorting function const getSortFunction = (sortBy: string) => { switch (sortBy) { From 170058de9f157b06b0f0a7fe91a89b0dd159a63a Mon Sep 17 00:00:00 2001 From: inunice Date: Mon, 5 May 2025 10:14:26 +0800 Subject: [PATCH 03/17] fix: infinite sorting --- app/src/contexts/TripSearchContext.tsx | 44 ++++++++++++++++---------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/app/src/contexts/TripSearchContext.tsx b/app/src/contexts/TripSearchContext.tsx index 9074d0a..71da40f 100644 --- a/app/src/contexts/TripSearchContext.tsx +++ b/app/src/contexts/TripSearchContext.tsx @@ -33,10 +33,26 @@ export function TripSearchProvider({ children }: { children: ReactNode }) { const [trip, setTrip] = useState(null); const [filters, setFilters] = useState(FILTER_INITIAL_STATE); - const [filteredTrips, setFilteredTrips] = useState([]); const [suggestedTrips, setSuggestedTrips] = useState([]); const [tripEndpoints, setTripEndpoints] = useState>({}); + const filteredTrips = React.useMemo(() => { + const { timeToLeave, sortBy, transportModes } = filters; + console.debug( + "[TripSearch] recompute filteredTrips ⇒ sortBy:", + sortBy, + "count:", + suggestedTrips.length, + ); + return [...suggestedTrips] + .filter((trip) => + trip.segments.every( + ({ segmentMode }) => segmentMode === "Walk" || transportModes.includes(segmentMode), + ), + ) + .sort(getSortFunction(sortBy)); + }, [suggestedTrips, filters]); + const updateTripEndpoints = (details: Partial) => { setTripEndpoints((prev) => ({ ...prev, ...details })); }; @@ -47,7 +63,6 @@ export function TripSearchProvider({ children }: { children: ReactNode }) { const existingTrips = await fetchTripData(trip, 1500); const fullTrips = await appendWalkingSegments(user.id, existingTrips, trip); setSuggestedTrips(fullTrips); - applyFilters(filters, fullTrips); } catch (error) { console.error("[ERROR] Fetching suggester trips: ", error); throw new Error("Failed to fetch trips"); @@ -55,21 +70,18 @@ export function TripSearchProvider({ children }: { children: ReactNode }) { }; const applyFilters = useCallback( - ({ timeToLeave, sortBy, transportModes }: FilterState, fullTrips?: TripSearch[]) => { - console.debug("[TripSearch] applyFilters ⇢", sortBy); - const currTrip = fullTrips ?? suggestedTrips; - - const filtered = [...currTrip] - .filter((trip) => - trip.segments.every( - ({ segmentMode }) => segmentMode === "Walk" || transportModes.includes(segmentMode), - ), - ) - .sort(getSortFunction(sortBy)); - - setFilteredTrips(filtered); + ({ timeToLeave, sortBy, transportModes }: FilterState) => { + // Only update if something actually changed + if ( + !dateEqual(timeToLeave, filters.timeToLeave) || + sortBy !== filters.sortBy || + !arrayEqual(transportModes, filters.transportModes) + ) { + console.debug("[TripSearch] setFilters", { timeToLeave, sortBy, transportModes }); + setFilters({ timeToLeave, sortBy, transportModes }); + } }, - [suggestedTrips, filters], + [filters], ); const value = { From 99a33b37ea72d4a652be660791ce025727e2fbc7 Mon Sep 17 00:00:00 2001 From: inunice Date: Mon, 5 May 2025 10:29:38 +0800 Subject: [PATCH 04/17] fix: party bug (hopefully) --- app/src/components/search/FilterSearch.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/components/search/FilterSearch.tsx b/app/src/components/search/FilterSearch.tsx index 8e0bc2c..91b8cb0 100644 --- a/app/src/components/search/FilterSearch.tsx +++ b/app/src/components/search/FilterSearch.tsx @@ -20,6 +20,8 @@ export default function FilterSearch({ sheetRef, filters, applyFilters }: Props) const [sortBy, setSortBy] = useState(filters.sortBy); const [selectedModes, setSelectedModes] = useState(filters.transportModes); + const hasAppliedRef = React.useRef(false); + // Toggle transport mode selection const toggleTransportMode = (mode: string) => { setSelectedModes((prev) => @@ -40,6 +42,7 @@ export default function FilterSearch({ sheetRef, filters, applyFilters }: Props) if (!isUnchanged) { applyFilters({ timeToLeave, sortBy, transportModes: selectedModes }); } + hasAppliedRef.current = true; // <— mark as handled sheetRef.current?.close(); }; @@ -49,7 +52,10 @@ export default function FilterSearch({ sheetRef, filters, applyFilters }: Props) snapPoints={snapPoints} index={-1} enablePanDownToClose - onClose={handleApplyFilters} + onClose={() => { + if (!hasAppliedRef.current) handleApplyFilters(); + hasAppliedRef.current = false; // reset for next open + }} > From 6221ad8a13627568759a4838ed27ae9d90e626dd Mon Sep 17 00:00:00 2001 From: inunice Date: Mon, 5 May 2025 10:35:40 +0800 Subject: [PATCH 05/17] fix: party bug (hopefully) --- app/src/components/search/FilterSearch.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/components/search/FilterSearch.tsx b/app/src/components/search/FilterSearch.tsx index 91b8cb0..e79a29a 100644 --- a/app/src/components/search/FilterSearch.tsx +++ b/app/src/components/search/FilterSearch.tsx @@ -53,7 +53,11 @@ export default function FilterSearch({ sheetRef, filters, applyFilters }: Props) index={-1} enablePanDownToClose onClose={() => { - if (!hasAppliedRef.current) handleApplyFilters(); + if (!hasAppliedRef.current) { + setTimeToLeave(filters.timeToLeave); + setSortBy(filters.sortBy); + setSelectedModes(filters.transportModes); + } hasAppliedRef.current = false; // reset for next open }} > From e92ceccacb971564600af5c522b5311adbf62972 Mon Sep 17 00:00:00 2001 From: inunice Date: Mon, 5 May 2025 10:38:13 +0800 Subject: [PATCH 06/17] fix: duration sorting --- app/src/contexts/TripSearchContext.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/contexts/TripSearchContext.tsx b/app/src/contexts/TripSearchContext.tsx index 71da40f..4b0f1f0 100644 --- a/app/src/contexts/TripSearchContext.tsx +++ b/app/src/contexts/TripSearchContext.tsx @@ -159,6 +159,7 @@ async function appendWalkingSegments( newTrip.startCoords = endpoints.startCoords; newTrip.segments.unshift(start); newTrip.preSegment = cleanStart; + newTrip.duration += start.duration; } if (end) { const { id, createdAt, updatedAt, ...cleanEnd } = end; @@ -166,6 +167,7 @@ async function appendWalkingSegments( newTrip.endCoords = endpoints.endCoords; newTrip.segments.push(end); newTrip.postSegment = cleanEnd; + newTrip.duration += end.duration; } return newTrip; From a930710d2f644beb36be9b552955edb9474227df Mon Sep 17 00:00:00 2001 From: inunice Date: Mon, 5 May 2025 10:38:39 +0800 Subject: [PATCH 07/17] build: bump version --- app/app.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/app.config.ts b/app/app.config.ts index 0eb2ea6..21ff5fc 100644 --- a/app/app.config.ts +++ b/app/app.config.ts @@ -8,7 +8,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({ ...config, slug: "Lakbayan", name: "Lakbayan", - version: "1.0.0", + version: "1.0.1", orientation: "portrait", scheme: "myapp", userInterfaceStyle: "automatic", From 242eb6ac1f036c9615455e8827746f3fbea002e9 Mon Sep 17 00:00:00 2001 From: inunice Date: Mon, 5 May 2025 10:47:16 +0800 Subject: [PATCH 08/17] feat: add source & destination to contributor profile --- app/src/app/(social)/contributor-account.tsx | 5 +++ app/src/contexts/LocationContext.tsx | 34 ++++++++++++-------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/app/src/app/(social)/contributor-account.tsx b/app/src/app/(social)/contributor-account.tsx index 0e91e6a..d42473d 100644 --- a/app/src/app/(social)/contributor-account.tsx +++ b/app/src/app/(social)/contributor-account.tsx @@ -18,6 +18,7 @@ import { import UserHeader from "@components/account/UserHeader"; import TripPreview from "@components/ui/TripPreview"; +import { SourceDestinationTitle } from "@components/ui/SourceDestinationTitle"; import { useUserTrips } from "@hooks/use-trip-data"; @@ -121,6 +122,10 @@ export default function ContributorAccount() { keyExtractor={(trip) => trip.id} renderItem={({ item }) => ( handleTripPress(item)}> + )} diff --git a/app/src/contexts/LocationContext.tsx b/app/src/contexts/LocationContext.tsx index 10a15a6..c488c27 100644 --- a/app/src/contexts/LocationContext.tsx +++ b/app/src/contexts/LocationContext.tsx @@ -1,5 +1,5 @@ import * as ExpoLocation from "expo-location"; -import { createContext, useContext, useState, useEffect, ReactNode } from "react"; +import React, { createContext, useContext, useState, useEffect, useRef, ReactNode } from "react"; import { AppState } from "react-native"; import { Alert } from "react-native"; @@ -77,7 +77,9 @@ export const LocationProvider: React.FC<{ children: ReactNode }> = ({ children } }, [permissionGranted]); return ( - + {children} ); @@ -88,16 +90,22 @@ export const useUserLocation = (): LocationContextType => { if (!context) { throw new Error("useLocation must be used within a LocationProvider"); } - if (!context.permissionGranted){ - Alert.alert( - "Location Permission Required", - "Lakbayan needs access to your location to function properly. Please go to your device's settings to enable it.", - [ - { - text: "OK", - }, - ], - ); - } + + // Show alert only the first time permission is seen as denied + const alertedRef = React.useRef(false); + useEffect(() => { + if (!context.permissionGranted && !alertedRef.current) { + Alert.alert( + "Location Permission Required", + "Lakbayan needs access to your location to function properly. Please enable it in Settings.", + [{ text: "OK" }], + ); + alertedRef.current = true; + } + if (context.permissionGranted) { + alertedRef.current = false; // reset so we can alert again if user revokes later + } + }, [context.permissionGranted]); + return context; }; From 79f760b19d590034fb2d3f692f2d4223e726b5a8 Mon Sep 17 00:00:00 2001 From: inunice Date: Mon, 5 May 2025 10:56:46 +0800 Subject: [PATCH 09/17] build: show version number in settings --- app/src/app/(tabs)/account.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/app/(tabs)/account.tsx b/app/src/app/(tabs)/account.tsx index 0fb7e0e..c89bee6 100644 --- a/app/src/app/(tabs)/account.tsx +++ b/app/src/app/(tabs)/account.tsx @@ -1,5 +1,6 @@ import React from "react"; import { Text, SafeAreaView, View, Alert, ScrollView } from "react-native"; +import Constants from "expo-constants"; import { logoutUser } from "@services/account-service"; import { useSession } from "@contexts/SessionContext"; @@ -20,6 +21,9 @@ export default function Account() { const { user, username } = useSession(); const { userRole, points, joinedDate, loading } = useAccountDetails(user?.id); + const appVersion = + Constants.expoConfig?.version ?? (Constants?.manifest as any)?.version ?? "dev"; + async function handleLogout() { try { await logoutUser(); @@ -96,6 +100,7 @@ export default function Account() { )} + Version {appVersion} From 4584a64a8ee6bf808872fdd5d1075d15d6155987 Mon Sep 17 00:00:00 2001 From: violessi <108144748+violessi@users.noreply.github.com> Date: Mon, 5 May 2025 10:58:07 +0800 Subject: [PATCH 10/17] fix: router replace instead of push --- app/src/app/(account)/bookmarked-trips.tsx | 9 +++------ app/src/app/(account)/submitted-trips.tsx | 9 +++------ app/src/app/(search)/1-search-trip.tsx | 2 +- app/src/app/(search)/2-trip-suggestions.tsx | 11 ++++++----- app/src/app/(tabs)/index.tsx | 2 +- app/src/components/ContributeOption.tsx | 2 +- app/src/components/TripListScreen.tsx | 2 +- app/src/components/search/RecentTrips.tsx | 4 ++-- 8 files changed, 18 insertions(+), 23 deletions(-) diff --git a/app/src/app/(account)/bookmarked-trips.tsx b/app/src/app/(account)/bookmarked-trips.tsx index 1d99b12..637801a 100644 --- a/app/src/app/(account)/bookmarked-trips.tsx +++ b/app/src/app/(account)/bookmarked-trips.tsx @@ -6,7 +6,7 @@ import { ActivityIndicator, Text, TouchableOpacity, - BackHandler + BackHandler, } from "react-native"; import { useRouter } from "expo-router"; import { useFocusEffect } from "@react-navigation/native"; @@ -33,7 +33,7 @@ export default function BookmarkedTrips() { postSegment: null, }; - router.push({ + router.replace({ pathname: "/(search)/3-trip-overview", params: { tripData: JSON.stringify(tripSearch), from: "bookmarked-trips" }, }); @@ -51,10 +51,7 @@ export default function BookmarkedTrips() { return true; }; - const backHandler = BackHandler.addEventListener( - "hardwareBackPress", - backAction, - ); + const backHandler = BackHandler.addEventListener("hardwareBackPress", backAction); return () => backHandler.remove(); }, []), diff --git a/app/src/app/(account)/submitted-trips.tsx b/app/src/app/(account)/submitted-trips.tsx index 22cb039..4b439df 100644 --- a/app/src/app/(account)/submitted-trips.tsx +++ b/app/src/app/(account)/submitted-trips.tsx @@ -25,7 +25,7 @@ export default function SubmittedTrips() { const { submittedTrips, loading } = useSubmittedTrips(user?.id || null); function handleTripPress(trip: TripSearch) { - router.push({ + router.replace({ pathname: "/(search)/3-trip-overview", params: { tripData: JSON.stringify(trip), from: "submitted-trips" }, }); @@ -43,10 +43,7 @@ export default function SubmittedTrips() { return true; }; - const backHandler = BackHandler.addEventListener( - "hardwareBackPress", - backAction, - ); + const backHandler = BackHandler.addEventListener("hardwareBackPress", backAction); return () => backHandler.remove(); }, []), @@ -54,7 +51,7 @@ export default function SubmittedTrips() { return ( -
+
{loading ? ( diff --git a/app/src/app/(search)/1-search-trip.tsx b/app/src/app/(search)/1-search-trip.tsx index d8c6139..7cc0c9e 100644 --- a/app/src/app/(search)/1-search-trip.tsx +++ b/app/src/app/(search)/1-search-trip.tsx @@ -59,7 +59,7 @@ export default function SearchTrip() { } try { await fetchSuggestedTrips(); - router.push("/(search)/2-trip-suggestions"); + router.replace("/(search)/2-trip-suggestions"); } catch (error) { Alert.alert("Error fetching trips. Please try again."); } diff --git a/app/src/app/(search)/2-trip-suggestions.tsx b/app/src/app/(search)/2-trip-suggestions.tsx index 49d6406..7e16619 100644 --- a/app/src/app/(search)/2-trip-suggestions.tsx +++ b/app/src/app/(search)/2-trip-suggestions.tsx @@ -2,7 +2,6 @@ import { useEffect, useRef } from "react"; import { useRouter } from "expo-router"; import BottomSheet from "@gorhom/bottom-sheet"; import { Text, SafeAreaView, View, Pressable } from "react-native"; -import { MaterialIcons } from "@expo/vector-icons"; import Header from "@components/ui/Header"; import TripPreview from "@components/ui/TripPreview"; @@ -47,9 +46,10 @@ export default function SuggestedTrips() { } }, []); - const handleSelectTrip = (trip: TripSearch) => { + const handleSelectTrip = (trip: TripSearch, index: number) => { setTrip(trip); - router.push("/(search)/3-trip-overview"); + console.log(`Selected trip: ${trip.id} with index ${index}`); + router.replace("/(search)/3-trip-overview"); }; const handleOpenFilters = () => filterSheetRef.current?.snapToIndex(1); @@ -70,10 +70,11 @@ export default function SuggestedTrips() { ) : ( - {filteredTrips.map((trip) => ( + {filteredTrips.map((trip, index) => ( handleSelectTrip(trip)} + onPress={() => handleSelectTrip(trip, index)} + android_ripple={{ color: "#ccc" }} > diff --git a/app/src/app/(tabs)/index.tsx b/app/src/app/(tabs)/index.tsx index c79bf89..863720d 100644 --- a/app/src/app/(tabs)/index.tsx +++ b/app/src/app/(tabs)/index.tsx @@ -18,7 +18,7 @@ export default function Index() { const { symbolRef, updateLiveStatus } = useLiveUpdates("box", 10); const handleTextInputFocus = () => { - router.push("/(search)/1-search-trip"); + router.replace("/(search)/1-search-trip"); }; const handleCameraChange = (state: MapBoxMapState) => { diff --git a/app/src/components/ContributeOption.tsx b/app/src/components/ContributeOption.tsx index 57034f1..9064f9a 100644 --- a/app/src/components/ContributeOption.tsx +++ b/app/src/components/ContributeOption.tsx @@ -14,7 +14,7 @@ interface OptionProps { const Option = ({ title, description, link, icon }: OptionProps) => { return ( router.push(link)} + onPress={() => router.replace(link)} className="flex-row items-center justify-between mx-4 border-b border-gray-200 py-6" > diff --git a/app/src/components/TripListScreen.tsx b/app/src/components/TripListScreen.tsx index dae0430..2cb8164 100644 --- a/app/src/components/TripListScreen.tsx +++ b/app/src/components/TripListScreen.tsx @@ -28,7 +28,7 @@ export default function TripListScreen({ const router = useRouter(); function handleTripPress(trip: TripSearch) { - router.push({ + router.replace({ pathname: "/(search)/3-trip-overview", params: { tripData: JSON.stringify(trip) }, }); diff --git a/app/src/components/search/RecentTrips.tsx b/app/src/components/search/RecentTrips.tsx index 346ccac..bf50120 100644 --- a/app/src/components/search/RecentTrips.tsx +++ b/app/src/components/search/RecentTrips.tsx @@ -27,7 +27,7 @@ export default function RecentTrips() { postSegment: null, }; - router.push({ + router.replace({ pathname: "/(search)/3-trip-overview", params: { tripData: JSON.stringify(tripSearch), from: "recent-trips" }, }); @@ -49,7 +49,7 @@ export default function RecentTrips() { trip={trip} onPress={() => { if (journal.status === "ongoing") { - router.push("/(journal)/transit-journal"); + router.replace("/(journal)/transit-journal"); } else if (trip) { handleTripPress(trip); } From 08954b6bc112aee03ca7b426274d1cd4ff261515 Mon Sep 17 00:00:00 2001 From: inunice Date: Mon, 5 May 2025 11:26:38 +0800 Subject: [PATCH 11/17] fix: no route name in editing walk --- app/src/components/contribute/RouteInformation.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/components/contribute/RouteInformation.tsx b/app/src/components/contribute/RouteInformation.tsx index 48f0dcc..c486e12 100644 --- a/app/src/components/contribute/RouteInformation.tsx +++ b/app/src/components/contribute/RouteInformation.tsx @@ -24,7 +24,12 @@ export default function RouteInformation({ const { route, updateRoute } = useTripCreator(); React.useEffect(() => { - if (route.segmentMode === "Walk" && !route.segmentName.toLowerCase().includes("walk")) { + console.log( + "[RouteInformation] route.segmentMode route.segmentName", + route.segmentMode, + route.segmentName, + ); + if (route.segmentMode === "Walk") { updateRoute({ segmentName: `Walk from ${route.startLocation} to ${route.endLocation}` }); } else if (route.segmentName.toLowerCase().includes("walk")) { updateRoute({ segmentName: "" }); From db63d25c2efd3622d4d52b8cb1960ac2cca19cd2 Mon Sep 17 00:00:00 2001 From: inunice Date: Mon, 5 May 2025 11:27:16 +0800 Subject: [PATCH 12/17] refactor: submit route -> transfer --- app/src/components/contribute/RouteInformation.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/components/contribute/RouteInformation.tsx b/app/src/components/contribute/RouteInformation.tsx index c486e12..ee16116 100644 --- a/app/src/components/contribute/RouteInformation.tsx +++ b/app/src/components/contribute/RouteInformation.tsx @@ -93,7 +93,7 @@ export default function RouteInformation({ handleEditRoute()}>Edit Route - handleSubmit()} /> + handleSubmit()} /> From df5f482a069be44b9f83094848b1dbc05e15d219 Mon Sep 17 00:00:00 2001 From: violessi <108144748+violessi@users.noreply.github.com> Date: Mon, 5 May 2025 11:44:14 +0800 Subject: [PATCH 13/17] fix: no name when editing walk segment --- app/src/app/(contribute)/2-review-trip.tsx | 2 ++ app/src/app/(contribute)/4-edit-transfer.tsx | 5 +++++ .../contribute/RouteInformation.tsx | 20 ++++++++----------- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/app/src/app/(contribute)/2-review-trip.tsx b/app/src/app/(contribute)/2-review-trip.tsx index 79fb3e8..7d5b08b 100644 --- a/app/src/app/(contribute)/2-review-trip.tsx +++ b/app/src/app/(contribute)/2-review-trip.tsx @@ -39,6 +39,8 @@ export default function TripReview() { submitTrip, } = useTripCreator(); if (!user) throw new Error("User must be logged in to create a trip!"); + console.log("Trips", trip); + console.log("Segments: ", segments); const handleCreateSegment = async () => { clearRouteData(); diff --git a/app/src/app/(contribute)/4-edit-transfer.tsx b/app/src/app/(contribute)/4-edit-transfer.tsx index 945175d..518417d 100644 --- a/app/src/app/(contribute)/4-edit-transfer.tsx +++ b/app/src/app/(contribute)/4-edit-transfer.tsx @@ -40,6 +40,9 @@ export default function RouteInput() { const [isEditingWaypoint, setIsEditingWaypoints] = useState(false); const [submitting, setSubmitting] = useState(false); + console.log("Trip name", trip.name); + console.log("Route name", route.segmentName); + // Calculate route on initial load for better UX useEffect(() => { if (!isEditingWaypoint && customWaypoints.length === 0) { @@ -90,6 +93,7 @@ export default function RouteInput() { setSubmitting(false); return; } + console.log("Submitted route name: ", route.segmentName); try { await addSegment(); clearRouteData(); @@ -169,6 +173,7 @@ export default function RouteInput() { sheetRef={bottomSheetRef} handleSubmit={handleSubmit} setIsEditingWaypoints={setIsEditingWaypoints} + isEditing={editingIndex !== -1} /> ); diff --git a/app/src/components/contribute/RouteInformation.tsx b/app/src/components/contribute/RouteInformation.tsx index ee16116..1946403 100644 --- a/app/src/components/contribute/RouteInformation.tsx +++ b/app/src/components/contribute/RouteInformation.tsx @@ -14,29 +14,25 @@ interface RouteInformationProps { sheetRef: React.RefObject; handleSubmit: () => void; setIsEditingWaypoints: React.Dispatch>; + isEditing: boolean; } export default function RouteInformation({ sheetRef, handleSubmit, setIsEditingWaypoints, + isEditing = false, }: RouteInformationProps) { const { route, updateRoute } = useTripCreator(); React.useEffect(() => { - console.log( - "[RouteInformation] route.segmentMode route.segmentName", - route.segmentMode, - route.segmentName, - ); - if (route.segmentMode === "Walk") { - updateRoute({ segmentName: `Walk from ${route.startLocation} to ${route.endLocation}` }); - } else if (route.segmentName.toLowerCase().includes("walk")) { - updateRoute({ segmentName: "" }); - } else { - updateRoute({ segmentName: route.segmentName }); + if (route.segmentMode === "Walk" && !isEditing) { + const defaultWalkName = `Walk from ${route.startLocation} to ${route.endLocation}`; + if (!route.segmentName || route.segmentName.startsWith("Walk from")) { + updateRoute({ segmentName: defaultWalkName }); + } } - }, [route.segmentMode]); + }, [route.segmentMode, route.startLocation, route.endLocation]); const handleEditRoute = () => { setIsEditingWaypoints(true); From a702b7eccfbbdd09e2e34095d7984082f669822e Mon Sep 17 00:00:00 2001 From: inunice Date: Mon, 5 May 2025 21:32:20 +0800 Subject: [PATCH 14/17] fix: disappearing destination --- app/src/app/(contribute)/3-add-transfer.tsx | 8 +++++++- app/src/components/map/TodaMarkerModal.tsx | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/app/(contribute)/3-add-transfer.tsx b/app/src/app/(contribute)/3-add-transfer.tsx index 0485aa2..3392e6f 100644 --- a/app/src/app/(contribute)/3-add-transfer.tsx +++ b/app/src/app/(contribute)/3-add-transfer.tsx @@ -1,3 +1,4 @@ +import { useMemo } from "react"; import { router } from "expo-router"; import { SafeAreaView, View, Alert, BackHandler } from "react-native"; import { useFocusEffect } from "@react-navigation/native"; @@ -54,13 +55,18 @@ export default function RouteSelectInfo() { return () => backHandler.remove(); }); + const memoizedEnd: [string | null, [number, number] | null] = useMemo( + () => [route.endLocation ?? null, route.endCoords ?? null], + [route.endLocation, route.endCoords], + ); + return (
handleEndChange(c, l)} /> {}}> - {stop.name} TODA + {stop.name} {modVerifications} From 4ba62798b7a14bbd211f1a57236fd5c36cd5e697 Mon Sep 17 00:00:00 2001 From: violessi <108144748+violessi@users.noreply.github.com> Date: Tue, 6 May 2025 12:12:39 +0800 Subject: [PATCH 15/17] feat: add loading when submitting --- app/src/app/(contribute)/2-review-trip.tsx | 28 ++++++++++++----- app/src/app/(contribute)/4-edit-transfer.tsx | 3 -- app/src/app/(journal)/journal-review.tsx | 33 ++++++++++++++------ 3 files changed, 44 insertions(+), 20 deletions(-) diff --git a/app/src/app/(contribute)/2-review-trip.tsx b/app/src/app/(contribute)/2-review-trip.tsx index 7d5b08b..3236b82 100644 --- a/app/src/app/(contribute)/2-review-trip.tsx +++ b/app/src/app/(contribute)/2-review-trip.tsx @@ -1,7 +1,7 @@ -import React, { useEffect } from "react"; +import React, { useState } from "react"; import { router } from "expo-router"; import { useFocusEffect } from "@react-navigation/native"; -import { SafeAreaView, View, Alert, BackHandler } from "react-native"; +import { ActivityIndicator, SafeAreaView, View, Alert, Text, BackHandler } from "react-native"; import Header from "@components/ui/Header"; import LineSource from "@components/map/LineSource"; @@ -22,6 +22,7 @@ import { fetchSubmitLogId, deleteSubmitLog, } from "@services/logs-service"; +import { set } from "lodash"; export default function TripReview() { const { user } = useSession(); @@ -38,9 +39,10 @@ export default function TripReview() { clearRouteData, submitTrip, } = useTripCreator(); + + const [isSubmitting, setIsSubmitting] = useState(false); + if (!user) throw new Error("User must be logged in to create a trip!"); - console.log("Trips", trip); - console.log("Segments: ", segments); const handleCreateSegment = async () => { clearRouteData(); @@ -61,18 +63,20 @@ export default function TripReview() { }; const handleSubmitTrip = async () => { + setIsSubmitting(true); try { await submitTrip(); - Alert.alert("Trip Submitted", "Your trip has been submitted successfully!"); const id = await fetchSubmitLogId({ userId: user.id }); if (!id) { console.error("No submit log ID found."); return; } - await updateSubmitLog({ id, status: "completed" }); + Alert.alert("Trip Submitted", "Your trip has been submitted successfully!"); + setIsSubmitting(false); router.replace("/(tabs)"); } catch (error) { + setIsSubmitting(false); Alert.alert("Error", "Failed to submit your trip. Please try again."); } }; @@ -115,7 +119,7 @@ export default function TripReview() { }); return ( - +
@@ -134,12 +138,20 @@ export default function TripReview() { deleteSegment={handleDeleteSegment} /> - + + + {isSubmitting && ( + + + Submitting trip, please wait... + + )} ); } diff --git a/app/src/app/(contribute)/4-edit-transfer.tsx b/app/src/app/(contribute)/4-edit-transfer.tsx index 518417d..56e2427 100644 --- a/app/src/app/(contribute)/4-edit-transfer.tsx +++ b/app/src/app/(contribute)/4-edit-transfer.tsx @@ -40,9 +40,6 @@ export default function RouteInput() { const [isEditingWaypoint, setIsEditingWaypoints] = useState(false); const [submitting, setSubmitting] = useState(false); - console.log("Trip name", trip.name); - console.log("Route name", route.segmentName); - // Calculate route on initial load for better UX useEffect(() => { if (!isEditingWaypoint && customWaypoints.length === 0) { diff --git a/app/src/app/(journal)/journal-review.tsx b/app/src/app/(journal)/journal-review.tsx index 7085525..69429da 100644 --- a/app/src/app/(journal)/journal-review.tsx +++ b/app/src/app/(journal)/journal-review.tsx @@ -1,6 +1,6 @@ import { useRouter } from "expo-router"; import React, { useState, useCallback } from "react"; -import { SafeAreaView, Alert, BackHandler } from "react-native"; +import { SafeAreaView, Alert, BackHandler, View, ActivityIndicator, Text } from "react-native"; import { useFocusEffect } from "@react-navigation/native"; import Header from "@components/ui/Header"; @@ -26,7 +26,8 @@ export default function JournalReview() { const router = useRouter(); const { user } = useSession(); const { cameraRef } = useMapView(); - const { trip, segments, transitJournal, rating, hasDeviated, setRating, setHasDeviated } = useTransitJournal(); + const { trip, segments, transitJournal, rating, hasDeviated, setRating, setHasDeviated } = + useTransitJournal(); const [newComment, setNewComment] = useState(""); const [isSubmitting, setIsSubmitting] = useState(false); @@ -39,8 +40,14 @@ export default function JournalReview() { const handleSubmit = async () => { setIsSubmitting(true); - if (!newComment.trim()) { setIsSubmitting(false); return; } - if (!trip || !segments || !user) { setIsSubmitting(false); return; } + if (!newComment.trim()) { + setIsSubmitting(false); + return; + } + if (!trip || !segments || !user) { + setIsSubmitting(false); + return; + } const journalPayload: Partial = { id: transitJournal.id, @@ -56,7 +63,10 @@ export default function JournalReview() { // // if did not deviate, increment GPS count if (!Boolean(hasDeviated)) { - await incrementSegmentGPSCount(trip.segments.map(({ id }) => id), !Boolean(hasDeviated)); + await incrementSegmentGPSCount( + trip.segments.map(({ id }) => id), + !Boolean(hasDeviated), + ); } await updateTransitJournal(journalPayload); await updateProfile({ id: user.id, transitJournalId: null }); @@ -65,6 +75,7 @@ export default function JournalReview() { setIsSubmitting(false); router.replace("/(tabs)"); } catch (error) { + setIsSubmitting(false); Alert.alert("Error", "Failed to submit your transit journal. Please try again."); } }; @@ -88,10 +99,7 @@ export default function JournalReview() { return true; }; - const backHandler = BackHandler.addEventListener( - "hardwareBackPress", - backAction, - ); + const backHandler = BackHandler.addEventListener("hardwareBackPress", backAction); return () => backHandler.remove(); }, []), @@ -122,6 +130,13 @@ export default function JournalReview() { setHasDeviated={setHasDeviated} isSubmitting={isSubmitting} /> + + {isSubmitting && ( + + + Submitting trip, please wait... + + )} ); } From de91c2d42c9d69c823fa640f108d73ba1b486e88 Mon Sep 17 00:00:00 2001 From: violessi <108144748+violessi@users.noreply.github.com> Date: Tue, 6 May 2025 12:24:45 +0800 Subject: [PATCH 16/17] feat: loading for toda submission --- app/src/app/(contribute)/toda-stops.tsx | 9 +++++++++ .../components/contribute/TodaInformation.tsx | 16 ++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/app/src/app/(contribute)/toda-stops.tsx b/app/src/app/(contribute)/toda-stops.tsx index d69a99c..1556f24 100644 --- a/app/src/app/(contribute)/toda-stops.tsx +++ b/app/src/app/(contribute)/toda-stops.tsx @@ -33,6 +33,7 @@ export default function TodaStops() { const [stops, setStops] = useState([]); const [loadingStops, setLoadingStops] = useState(false); const [formSnapshot, setFormSnapshot] = useState(""); + const [isSubmitting, setIsSubmitting] = useState(false); const loadStops = async () => { setLoadingStops(true); @@ -114,7 +115,15 @@ export default function TodaStops() { coordinates={coordinates} onNewStopAdded={loadStops} onFormChange={(form) => setFormSnapshot(JSON.stringify(form))} + setIsSubmitting={setIsSubmitting} + isSubmitting={isSubmitting} /> + {isSubmitting && ( + + + Submitting TODA stop, please wait... + + )} ); } diff --git a/app/src/components/contribute/TodaInformation.tsx b/app/src/components/contribute/TodaInformation.tsx index b541ffb..6ad4d73 100644 --- a/app/src/components/contribute/TodaInformation.tsx +++ b/app/src/components/contribute/TodaInformation.tsx @@ -41,9 +41,17 @@ interface TodaStopsProps { coordinates: Coordinates | null; onNewStopAdded: () => void; onFormChange?: (form: { todaName: string; color: string; landmark: string }) => void; + setIsSubmitting?: React.Dispatch>; + isSubmitting?: boolean; } -export default function TodaStops({ coordinates, onNewStopAdded, onFormChange }: TodaStopsProps) { +export default function TodaStops({ + coordinates, + onNewStopAdded, + onFormChange, + setIsSubmitting, + isSubmitting, +}: TodaStopsProps) { const { user } = useSession(); const [form, setForm] = useState({ todaName: "", color: "", landmark: "" }); const [dialogVisible, setDialogVisible] = useState(false); @@ -60,8 +68,10 @@ export default function TodaStops({ coordinates, onNewStopAdded, onFormChange }: }; const handleSubmit = async () => { + setIsSubmitting?.(true); if (!coordinates) { Alert.alert("No pins set!", "Please select a location on the map."); + setIsSubmitting?.(false); return; } @@ -97,8 +107,10 @@ export default function TodaStops({ coordinates, onNewStopAdded, onFormChange }: Alert.alert("Success!", "TODA stop information submitted successfully!"); resetForm(); onNewStopAdded(); + setIsSubmitting?.(false); } catch (error: any) { console.error("Error submitting TODA stop:", error); + setIsSubmitting?.(false); Alert.alert("Submission failed", "Something went wrong. Please try again."); } }; @@ -132,7 +144,7 @@ export default function TodaStops({ coordinates, onNewStopAdded, onFormChange }: /> - + setDialogVisible(false)}> From 5931312bc2d6ed1ad13e46a55f958458193e43ed Mon Sep 17 00:00:00 2001 From: violessi <108144748+violessi@users.noreply.github.com> Date: Tue, 6 May 2025 12:28:17 +0800 Subject: [PATCH 17/17] feat: loading when submitting comment --- app/src/app/(social)/comments-list.tsx | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/app/src/app/(social)/comments-list.tsx b/app/src/app/(social)/comments-list.tsx index 72d884a..75b5429 100644 --- a/app/src/app/(social)/comments-list.tsx +++ b/app/src/app/(social)/comments-list.tsx @@ -8,6 +8,7 @@ import { getComments, addComment } from "@services/socials-service"; import Header from "@components/ui/Header"; import CommentItem from "@components/ui/CommentItem"; import PrimaryButton from "@components/ui/PrimaryButton"; +import { set } from "lodash"; export default function CommentsList() { const { user } = useSession(); @@ -18,6 +19,7 @@ export default function CommentsList() { const [comments, setComments] = useState([]); const [content, setContent] = useState(""); const [loading, setLoading] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); useEffect(() => { async function fetchComments() { @@ -36,14 +38,19 @@ export default function CommentsList() { }, [tripId]); const handleCommentSubmit = async () => { - if (!content.trim()) return; - + setIsSubmitting(true); + if (!content.trim()) { + setIsSubmitting(false); + return; + } try { await addComment(tripId, user?.id || "", content, isGpsVerified); const updatedComments = await getComments(tripId); setComments(updatedComments || []); setContent(""); + setIsSubmitting(false); } catch (error) { + setIsSubmitting(false); console.error("Error adding comment:", error); } }; @@ -84,7 +91,11 @@ export default function CommentsList() { className="flex-1 border border-gray-200 rounded-lg px-3" testID="comment-input" /> - +