From 308dabd31dd9a486d9720165f7c34f4fa7a0e247 Mon Sep 17 00:00:00 2001 From: baburama Date: Sun, 10 Aug 2025 11:49:43 -0400 Subject: [PATCH] Error handling and human readable address Add error handling for pin submission and make all ui elements address readable --- frontend/src/app/Components/Overlay.tsx | 176 ++++++++++++++++++++---- frontend/src/app/utils/addOutlet.ts | 111 ++++++++++++--- 2 files changed, 244 insertions(+), 43 deletions(-) diff --git a/frontend/src/app/Components/Overlay.tsx b/frontend/src/app/Components/Overlay.tsx index 48d9058..bc85bc5 100644 --- a/frontend/src/app/Components/Overlay.tsx +++ b/frontend/src/app/Components/Overlay.tsx @@ -1,7 +1,7 @@ "use client"; import React, {useState, useEffect} from 'react'; -import { addOutletFrontend } from "../utils/addOutlet"; +import { addOutletFrontend, getAddressFromCoordinates } from "../utils/addOutlet"; import { isOnLand } from "../utils/addOutlet"; import { LucideBookmark, LucideClock, LucidePlus, LucideSearch, LucideUpload, SunMedium, Moon, ChevronDown, Plus } from 'lucide-react'; import { auth, db } from "../firebase/firebase"; @@ -42,12 +42,17 @@ const AddOutlet: React.FC = ({ const [searchResults, setSearchResults] = useState>([]); const [showSearchResults, setShowSearchResults] = useState(false); const [showProfile, setProfile ] = useState("") + const [pinAddress, setPinAddress] = useState(""); + const [userName, setUserName] = useState(""); const [userEmail, setUserEmail] = useState(""); const router = useRouter(); + const [isLoading, setIsLoading] = useState(false); + const [errorMessage, setErrorMessage] = useState(""); + const [showErrorPopup, setShowErrorPopup] = useState(false); useEffect(() => { const fetchUserData = async () => { try { @@ -81,10 +86,36 @@ const AddOutlet: React.FC = ({ //if coordinates exist, will fill them in for address useEffect(() => { - if (coords) { - setAddress(`${coords?.lng.toFixed(5)} ${coords?.lat.toFixed(5)}`); - } + const updateAddressFromCoords = async () => { + if (coords) { + try { + const address = await getAddressFromCoordinates(coords.lat, coords.lng); + setAddress(address); + } catch (error) { + console.error('Failed to get address from coordinates:', error); + // Fallback to coordinate format + setAddress(`${coords.lng.toFixed(5)}, ${coords.lat.toFixed(5)}`); + } + } + }; + + updateAddressFromCoords(); }, [coords]); + useEffect(() => { + const updatePinAddress = async () => { + if (coords && showPinOverlay) { + try { + const address = await getAddressFromCoordinates(coords.lat, coords.lng); + setPinAddress(address); + } catch (error) { + console.error('Failed to get address for pin:', error); + setPinAddress(`${coords.lng.toFixed(5)}, ${coords.lat.toFixed(5)}`); + } + } + }; + + updatePinAddress(); + }, [coords, showPinOverlay]); const handleSearch = async (value: string) => { @@ -377,27 +408,76 @@ const AddOutlet: React.FC = ({
- + if (!trimmedAddress || trimmedAddress.length < 3) { + setErrorMessage("Please enter a valid address (at least 3 characters)."); + setShowErrorPopup(true); + return; + } + + if (outletCount <= 0) { + setErrorMessage("Number of outlets must be greater than 0."); + setShowErrorPopup(true); + return; + } + + // Check for obviously invalid addresses before submitting + if (/^[0-9\s\-\.]+$/.test(trimmedAddress)) { + setErrorMessage("Please enter a complete address, not just coordinates or numbers."); + setShowErrorPopup(true); + return; + } + + setIsLoading(true); + setErrorMessage(""); + setShowErrorPopup(false); + + try { + await addOutletFrontend({ + userName: userName || "TestUser", + userId: auth.currentUser?.uid || "user123", + locationName: trimmedAddress, + chargerType: powerType || selectedPort, + description: `Condition: ${selectedCondition}. ${extraDetails}`, + }); + + setShowAddOutlet(false); + // Reset form + setAddress(""); + setOutletCount(1); + setPowerType(""); + setSelectedPort("Triple Peg"); + setSelectedCondition("New"); + setExtraDetails(""); + } catch (err: any) { + console.error("Error submitting outlet:", err); + setErrorMessage(err.message || "An error occurred while submitting the outlet."); + setShowErrorPopup(true); + } finally { + setIsLoading(false); + } + }} + + disabled={isLoading} + className={`text-md font-semibold rounded-4xl mt-3 relative z-60 pl-4 pr-4 p-1.5 flex items-center justify-center min-w-[80px] ${ + isLoading + ? "bg-lime-600 cursor-not-allowed" + : "bg-lime-700 hover:bg-lime-600" + }`} + > + {isLoading ? ( + <> +
+ Loading... + + ) : ( + "Submit" + )} +
)} @@ -411,6 +491,15 @@ const AddOutlet: React.FC = ({

You dropped a pin!

+
+

+ Address +

+
+ {pinAddress || "Loading address..."} +
+
+

Longitude: {coords?.lng.toFixed(5)}

@@ -503,6 +592,45 @@ const AddOutlet: React.FC = ({ )} + {showErrorPopup && ( +
+
+ + +
+
+ ! +
+
+

+ Validation Error +

+

+ {errorMessage} +

+ +
+
+
+
+ )} + ); }; diff --git a/frontend/src/app/utils/addOutlet.ts b/frontend/src/app/utils/addOutlet.ts index 24a25eb..64da1d3 100644 --- a/frontend/src/app/utils/addOutlet.ts +++ b/frontend/src/app/utils/addOutlet.ts @@ -77,34 +77,94 @@ interface FrontendOutletInput { // Geocoding helper async function getLatLngFromAddress(address: string): Promise<{ lat: number; lng: number }> { const encoded = encodeURIComponent(address); - const url = `https://nominatim.openstreetmap.org/search?format=json&q=${encoded}`; + const url = `https://nominatim.openstreetmap.org/search?format=json&q=${encoded}&limit=1`; - const response = await fetch(url); - const data = await response.json(); + try { + const response = await fetch(url); + + if (!response.ok) { + throw new Error("Geocoding service is currently unavailable."); + } + + const data = await response.json(); + + if (!data || data.length === 0) { + throw new Error("Address not found. Please enter a valid address (e.g., '123 Main St, City, State' or 'Central Park, New York')."); + } + + const result = data[0]; + + // Check if the result has proper coordinates + if (!result.lat || !result.lon || isNaN(parseFloat(result.lat)) || isNaN(parseFloat(result.lon))) { + throw new Error("Invalid address coordinates returned. Please try a more specific address."); + } - if (!data || data.length === 0) { - throw new Error("Unable to geocode address."); + return { + lat: parseFloat(result.lat), + lng: parseFloat(result.lon), + }; + } catch (error) { + if (error instanceof Error) { + throw error; + } + throw new Error("Failed to validate address. Please check your internet connection and try again."); } +} +export async function getAddressFromCoordinates(lat: number, lng: number): Promise { + const url = `https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&addressdetails=1`; + + try { + const response = await fetch(url); + + if (!response.ok) { + throw new Error("Reverse geocoding service unavailable"); + } + + const data = await response.json(); + + if (!data || !data.display_name) { + return `${lng.toFixed(5)}, ${lat.toFixed(5)}`; // fallback to coordinates + } - return { - lat: parseFloat(data[0].lat), - lng: parseFloat(data[0].lon), - }; + return data.display_name; + } catch (error) { + console.error('Reverse geocoding failed:', error); + return `${lng.toFixed(5)}, ${lat.toFixed(5)}`; // fallback to coordinates + } } // Frontend wrapper function export async function addOutletFrontend(input: FrontendOutletInput): Promise { try { - const locationName = input.locationName.trim(); const chargerType = input.chargerType.trim(); const userName = input.userName.trim(); const userId = input.userId.trim(); const description = input.description.trim(); - // --- Basic Validation --- - if (!locationName || locationName.length < 5) { - throw new Error("Please enter a valid address (5+ characters)."); + // --- Enhanced Address Validation --- + if (!locationName || locationName.length < 3) { + throw new Error("Please enter a valid address (at least 3 characters)."); + } + + // Check for obviously invalid addresses + if (/^[0-9\s\-\.]+$/.test(locationName)) { + throw new Error("Please enter a complete address, not just coordinates or numbers."); + } + + // Check for common invalid patterns + const invalidPatterns = [ + /^test$/i, + /^abc+$/i, + /^123+$/i, + /^[a-z]$/i, // single letter + /^\s*$/, // only whitespace + /^\.+$/, // only dots + /^-+$/, // only dashes + ]; + + if (invalidPatterns.some(pattern => pattern.test(locationName))) { + throw new Error("Please enter a real address (e.g., '123 Main St, City' or 'Times Square, NYC')."); } if (!chargerType || chargerType.length < 2) { @@ -118,12 +178,24 @@ export async function addOutletFrontend(input: FrontendOutletInput): Promise 180 ) { - throw new Error("Geocoding returned invalid coordinates."); + throw new Error("Invalid location coordinates. Please enter a valid address."); } - + + // Check if location is on land if (!isOnLand(lat, lng)) { - throw new Error("The selected location is not on land."); + throw new Error("The location appears to be in water. Please enter an address on land."); } await addOutlet({