11// --- Imports ---
2- import React , { useState , useEffect } from "react" ;
2+ import React , { useState , useEffect , useCallback } from "react" ;
33import { MapPinIcon , UserCircleIcon } from "@heroicons/react/24/solid" ;
44import { Link , useNavigate } from "react-router-dom" ;
55import { useAuth } from "../../context/AuthContext" ;
@@ -26,15 +26,15 @@ L.Icon.Default.mergeOptions({
2626} ) ;
2727
2828// --- Main Header Component ---
29- const Header : React . FC < HeaderProps > = ( { className, manualLocation } ) => {
29+ const Header : React . FC < HeaderProps > = ( { className } ) => {
3030 const navigate = useNavigate ( ) ;
31- const { isAuthenticated , isLoading : isAuthLoading } = useAuth ( ) ;
32-
33- // --- State: Geolocation for map modal ---
34- const [ geoLocation , setGeoLocation ] = useState < {
35- latitude : number ;
36- longitude : number ;
37- } | null > ( null ) ;
31+ const {
32+ isAuthenticated ,
33+ isLoading : isAuthLoading ,
34+ location ,
35+ setLocation ,
36+ locationStatus ,
37+ } = useAuth ( ) ;
3838
3939 // --- State: User profile ---
4040 const [ profile , setProfile ] = useState < any > ( null ) ;
@@ -66,30 +66,8 @@ const Header: React.FC<HeaderProps> = ({ className, manualLocation }) => {
6666 // --- State: Show/hide map modal ---
6767 const [ showMap , setShowMap ] = useState ( false ) ;
6868
69- // Effect: fetch user profile and location info after auth
70- // --- Effect: Fetch user profile and detect location on mount ---
69+ // --- Effect: Fetch user profile and update location address ---
7170 useEffect ( ( ) => {
72- // Helper: retry fetch with delay (for OpenStreetMap reverse geocoding)
73- const fetchWithRetry = async (
74- url : string ,
75- attempts : number ,
76- delayMs : number ,
77- ) : Promise < any > => {
78- for ( let i = 0 ; i < attempts ; i ++ ) {
79- try {
80- const res = await fetch ( url ) ;
81- if ( ! res . ok ) throw new Error ( "Fetch failed" ) ;
82- return await res . json ( ) ;
83- } catch ( err ) {
84- if ( i < attempts - 1 ) {
85- await new Promise ( ( resolve ) => setTimeout ( resolve , delayMs ) ) ;
86- }
87- }
88- }
89- throw new Error ( "All fetch attempts failed" ) ;
90- } ;
91-
92- // Main: load profile and location
9371 const loadInitialData = async ( ) => {
9472 // Fetch user profile if authenticated
9573 if ( isAuthenticated ) {
@@ -100,60 +78,108 @@ const Header: React.FC<HeaderProps> = ({ className, manualLocation }) => {
10078 /* Profile fetch failed */
10179 }
10280 }
81+
82+ // Handle location address display
10383 setLocationLoading ( true ) ;
104- // Request geolocation from browser
105- if ( navigator . geolocation ) {
106- navigator . geolocation . getCurrentPosition (
107- async ( position ) => {
108- const { latitude, longitude } = position . coords ;
109- setGeoLocation ( { latitude, longitude } ) ;
110- try {
111- const data = await fetchWithRetry (
112- `https://nominatim.openstreetmap.org/reverse?format=json&lat=${ latitude } &lon=${ longitude } ` ,
113- 3 , // attempts
114- 1500 , // delay ms
115- ) ;
116- const province =
117- data . address . county ||
118- data . address . state ||
119- data . address . region ||
120- data . address . province ||
121- "" ;
122- const municipality =
123- data . address . city ||
124- data . address . town ||
125- data . address . village ||
126- "" ;
127- setUserProvince ( province ) ;
128- setUserAddress ( municipality ) ;
129- setLocationLoading ( false ) ;
130- } catch ( err ) {
131- setUserAddress ( "Could not determine address" ) ;
132- setUserProvince ( "" ) ;
133- setLocationLoading ( false ) ;
134- }
135- } ,
136- ( ) => {
137- // Permission denied or error
84+ if ( locationStatus === "allowed" && location ) {
85+ // Check if we have cached address data
86+ const cachedAddress = localStorage . getItem (
87+ `address_${ location . latitude } _${ location . longitude } ` ,
88+ ) ;
89+ if ( cachedAddress ) {
90+ try {
91+ const { address, province } = JSON . parse ( cachedAddress ) ;
92+ setUserAddress ( address ) ;
93+ setUserProvince ( province ) ;
13894 setLocationLoading ( false ) ;
139- setUserAddress ( "" ) ;
95+ return ;
96+ } catch {
97+ // Cache is corrupted, continue with API call
98+ }
99+ }
100+
101+ // Fetch address from API
102+ try {
103+ const res = await fetch (
104+ `https://nominatim.openstreetmap.org/reverse?format=json&lat=${ location . latitude } &lon=${ location . longitude } ` ,
105+ ) ;
106+ const data = await res . json ( ) ;
107+ if ( data && data . address ) {
108+ const { road, suburb, city, town, village, county, state } =
109+ data . address ;
110+ const province =
111+ county ||
112+ state ||
113+ data . address . region ||
114+ data . address . province ||
115+ "" ;
116+ const streetPart = road || "" ;
117+ const areaPart = suburb || village || "" ;
118+ const cityPart = city || town || "" ;
119+ const fullAddress = [ streetPart , areaPart , cityPart ]
120+ . filter ( Boolean )
121+ . join ( ", " ) ;
122+ const finalAddress = fullAddress || "Could not determine address" ;
123+
124+ setUserAddress ( finalAddress ) ;
125+ setUserProvince ( province ) ;
126+
127+ // Cache the address for faster subsequent loads
128+ localStorage . setItem (
129+ `address_${ location . latitude } _${ location . longitude } ` ,
130+ JSON . stringify ( { address : finalAddress , province } ) ,
131+ ) ;
132+ } else {
133+ setUserAddress ( "Could not determine address" ) ;
140134 setUserProvince ( "" ) ;
141- setGeoLocation ( null ) ;
142- } ,
143- ) ;
135+ }
136+ } catch ( error ) {
137+ setUserAddress ( "Could not determine address" ) ;
138+ setUserProvince ( "" ) ;
139+ } finally {
140+ setLocationLoading ( false ) ;
141+ }
144142 } else {
143+ // Handle cases where location is not yet known or denied
145144 setLocationLoading ( false ) ;
146- setUserAddress ( "" ) ;
145+ switch ( locationStatus ) {
146+ case "denied" :
147+ setUserAddress ( "Location not shared" ) ;
148+ break ;
149+ case "not_set" :
150+ case "unsupported" :
151+ default :
152+ setUserAddress ( "Location not set" ) ;
153+ break ;
154+ }
147155 setUserProvince ( "" ) ;
148- setGeoLocation ( null ) ;
149156 }
150157 } ;
158+
151159 if ( ! isAuthLoading ) {
152160 loadInitialData ( ) ;
153161 }
154- // Only run once after auth loads
155- // eslint-disable-next-line react-hooks/exhaustive-deps
156- } , [ isAuthenticated , isAuthLoading ] ) ;
162+ } , [ isAuthenticated , isAuthLoading , location , locationStatus ] ) ;
163+ const handleRequestLocation = useCallback ( ( ) => {
164+ setLocationLoading ( true ) ;
165+ setUserAddress ( "Detecting location..." ) ;
166+ if ( navigator . geolocation ) {
167+ navigator . geolocation . getCurrentPosition (
168+ ( position ) => {
169+ const { latitude, longitude } = position . coords ;
170+ setLocation ( "allowed" , { latitude, longitude } ) ;
171+ } ,
172+ ( error ) => {
173+ console . error ( "Error getting location:" , error ) ;
174+ setLocation ( "denied" ) ;
175+ setLocationLoading ( false ) ;
176+ } ,
177+ ) ;
178+ } else {
179+ setLocation ( "unsupported" ) ;
180+ setLocationLoading ( false ) ;
181+ }
182+ } , [ setLocation ] ) ;
157183
158184 // --- Effect: Randomize search bar placeholder after location loads ---
159185 useEffect ( ( ) => {
@@ -176,14 +202,14 @@ const Header: React.FC<HeaderProps> = ({ className, manualLocation }) => {
176202
177203 // --- Map Modal: Shows user's detected location on a map ---
178204 const MapModal : React . FC = ( ) => {
179- if ( ! geoLocation || ! geoLocation . latitude || ! geoLocation . longitude )
180- return null ;
181- // Close modal if background is clicked
205+ if ( ! location || ! location . latitude || ! location . longitude ) return null ;
206+
182207 const handleBackdropClick = ( e : React . MouseEvent < HTMLDivElement > ) => {
183208 if ( e . target === e . currentTarget ) {
184209 setShowMap ( false ) ;
185210 }
186211 } ;
212+
187213 return (
188214 < div
189215 className = "fixed inset-0 z-50 flex items-center justify-center bg-black/70"
@@ -203,7 +229,7 @@ const Header: React.FC<HeaderProps> = ({ className, manualLocation }) => {
203229 </ button >
204230 < div className = "flex-1 overflow-hidden rounded-b-lg" >
205231 < MapContainer
206- center = { [ geoLocation . latitude , geoLocation . longitude ] }
232+ center = { [ location . latitude , location . longitude ] }
207233 zoom = { 16 }
208234 scrollWheelZoom = { true }
209235 style = { { height : "100%" , width : "100%" } }
@@ -212,7 +238,7 @@ const Header: React.FC<HeaderProps> = ({ className, manualLocation }) => {
212238 attribution = '© <a href="https://osm.org/copyright">OpenStreetMap</a> contributors'
213239 url = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
214240 />
215- < Marker position = { [ geoLocation . latitude , geoLocation . longitude ] } >
241+ < Marker position = { [ location . latitude , location . longitude ] } >
216242 < Popup > You are here</ Popup >
217243 </ Marker >
218244 </ MapContainer >
@@ -301,7 +327,10 @@ const Header: React.FC<HeaderProps> = ({ className, manualLocation }) => {
301327 < span className = "animate-pulse text-gray-500" >
302328 Detecting location...
303329 </ span >
304- ) : userAddress && userProvince ? (
330+ ) : locationStatus === "allowed" &&
331+ location &&
332+ userAddress &&
333+ userProvince ? (
305334 < button
306335 type = "button"
307336 className = "text-left font-medium text-blue-900 transition-all duration-200 hover:text-lg hover:text-blue-700 focus:outline-none"
@@ -310,17 +339,22 @@ const Header: React.FC<HeaderProps> = ({ className, manualLocation }) => {
310339 >
311340 { userAddress } , { userProvince }
312341 </ button >
313- ) : manualLocation &&
314- manualLocation . municipality &&
315- manualLocation . province ? (
316- < span className = "text-left font-medium text-blue-900" >
317- { manualLocation . municipality } , { manualLocation . province }
318- </ span >
319342 ) : (
320- < span className = "text-left text- gray-500" > Location not set </ span >
343+ < span className = "text-gray-600" > { userAddress } </ span >
321344 ) }
322345 </ div >
323346 </ div >
347+ { ! locationLoading &&
348+ ( locationStatus === "denied" ||
349+ locationStatus === "not_set" ||
350+ locationStatus === "unsupported" ) && (
351+ < button
352+ onClick = { handleRequestLocation }
353+ className = "mt-2 w-full rounded-lg bg-yellow-300 p-2 text-center text-sm font-semibold text-blue-700 transition-colors hover:bg-yellow-400"
354+ >
355+ Share Location
356+ </ button >
357+ ) }
324358 { /* --- Search Bar for Service Queries --- */ }
325359 < form
326360 className = "mt-4 w-full"
0 commit comments