Skip to content

Commit cf7f5ee

Browse files
authored
Merge pull request #280 from map-of-pi/findme-geolocation
Approved (1).
2 parents 7836012 + 4b877bd commit cf7f5ee

File tree

6 files changed

+202
-83
lines changed

6 files changed

+202
-83
lines changed

.env.development

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ NEXT_PUBLIC_API_URL=http://localhost:8001/api/v1
88
NEXT_PUBLIC_PI_SDK_URL=https://sdk.minepi.com/pi-sdk.js
99

1010
NEXT_PUBLIC_SENTRY_DSN="ADD YOUR SENTRY DSN"
11-
NEXT_PUBLIC_IMAGE_PLACEHOLDER_URL="ADD YOUR IMAGE PLACEHOLDER URL"
11+
NEXT_PUBLIC_IMAGE_PLACEHOLDER_URL="ADD YOUR IMAGE PLACEHOLDER URL"

src/app/[locale]/page.tsx

+73-38
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
'use client';
22

33
import L from 'leaflet';
4-
54
import { useTranslations } from 'next-intl';
65
import dynamic from 'next/dynamic';
76
import Image from 'next/image';
@@ -11,7 +10,9 @@ import { useContext, useEffect, useState, useRef } from 'react';
1110
import { Button } from '@/components/shared/Forms/Buttons/Buttons';
1211
import SearchBar from '@/components/shared/SearchBar/SearchBar';
1312
import { fetchSellers } from '@/services/sellerApi';
14-
import { fetchUserLocation } from '@/services/userSettingsApi';
13+
import { fetchUserSettings } from '@/services/userSettingsApi';
14+
import { DeviceLocationType, IUserSettings } from '@/constants/types';
15+
import { userLocation } from '@/utils/geolocation';
1516

1617
import { AppContext } from '../../../context/AppContextProvider';
1718
import logger from '../../../logger.config.mjs';
@@ -23,58 +24,92 @@ export default function Index() {
2324
});
2425
const mapRef = useRef<L.Map | null>(null);
2526

26-
const [mapCenter, setMapCenter] = useState<{ lat: number; lng: number }>({
27-
lat: 0,
28-
lng: 0,
29-
});
27+
// State management with proper typing
28+
const [mapCenter, setMapCenter] = useState<{ lat: number; lng: number } | null>(null);
29+
const [searchCenter, setSearchCenter] = useState<{ lat: number; lng: number } | null>(null);
30+
const [findme, setFindme] = useState<DeviceLocationType>(DeviceLocationType.SearchCenter);
31+
const [dbUserSettings, setDbUserSettings] = useState<IUserSettings | null>(null);
3032
const [zoomLevel, setZoomLevel] = useState(2);
3133
const [locationError, setLocationError] = useState<string | null>(null);
3234
const [searchQuery, setSearchQuery] = useState<string>('');
3335
const [isSearchClicked, setSearchClicked] = useState(false);
3436
const [searchResults, setSearchResults] = useState<any[]>([]);
3537

36-
const { isSigningInUser } = useContext(AppContext);
37-
38-
// Default map center (example: New York City)
39-
const defaultMapCenter = { lat: 20, lng: -74.006 };
38+
const { isSigningInUser, currentUser, autoLoginUser, reload, setReload } = useContext(AppContext);
4039

4140
useEffect(() => {
42-
const fetchLocationOnLoad = async () => {
41+
setReload(false)
42+
if (!currentUser) {
43+
logger.info("User not logged in; attempting auto-login..");
44+
autoLoginUser();
45+
}
46+
47+
const getUserSettingsData = async () => {
4348
try {
44-
const location = await fetchUserLocation();
45-
setMapCenter(location.origin);
46-
setZoomLevel(location.radius);
47-
logger.info('User location obtained successfully on initial load:', {
48-
location,
49-
});
49+
const data = await fetchUserSettings();
50+
if (data) {
51+
logger.info('Fetched user settings data successfully:', { data });
52+
setDbUserSettings(data);
53+
if (data.search_map_center?.coordinates) {
54+
setSearchCenter({
55+
lat: data.search_map_center.coordinates[1],
56+
lng: data.search_map_center.coordinates[0],
57+
});
58+
}
59+
} else {
60+
logger.warn('User Settings not found.');
61+
setDbUserSettings(null);
62+
setSearchCenter(null)
63+
}
5064
} catch (error) {
51-
logger.error('Error getting location on initial load.', { error });
52-
setMapCenter(defaultMapCenter);
53-
setZoomLevel(2);
65+
logger.error('Error fetching user settings data:', { error });
5466
}
5567
};
5668

57-
fetchLocationOnLoad();
58-
}, [isSigningInUser]);
69+
getUserSettingsData();
70+
}, [currentUser, reload]);
71+
72+
useEffect(() => {
73+
const resolveLocation = async () => {
74+
if (dbUserSettings && dbUserSettings.findme !== DeviceLocationType.SearchCenter) {
75+
const loc = await userLocation(dbUserSettings);
76+
if (loc) {
77+
setSearchCenter({ lat: loc[0], lng: loc[1] });
78+
}
79+
else{
80+
setSearchCenter(null)
81+
}
82+
}
83+
};
84+
resolveLocation();
85+
}, [dbUserSettings]);
5986

6087
const handleLocationButtonClick = async () => {
61-
try {
62-
const location = await fetchUserLocation();
63-
setMapCenter(location.origin);
64-
setZoomLevel(location.radius);
65-
setLocationError(null);
66-
logger.info('User location obtained successfully on button click:', {
67-
location,
68-
});
69-
} catch (error) {
70-
logger.error('Error getting location on button click.', { error });
71-
setLocationError(
72-
t('HOME.LOCATION_SERVICES.ENABLE_LOCATION_SERVICES_MESSAGE'),
73-
);
88+
if (dbUserSettings) {
89+
const loc = await userLocation(dbUserSettings);
90+
if (loc) {
91+
setSearchCenter({ lat: loc[0], lng: loc[1] });
92+
logger.info('User location obtained successfully on button click:', { location });
93+
}
94+
else{
95+
setSearchCenter(null)
96+
}
7497
}
98+
// try {
99+
// setReload(true);
100+
// setLocationError(null);
101+
// logger.info('User location obtained successfully on button click:', { location });
102+
// } catch (error) {
103+
// setReload(false)
104+
// logger.error('Error getting location on button click.', { error });
105+
// setLocationError(t('HOME.LOCATION_SERVICES.ENABLE_LOCATION_SERVICES_MESSAGE'));
106+
// }
107+
// finally{
108+
// setReload(false);
109+
// }
75110
};
76111

77-
// handle search query update from SearchBar and associated results
112+
// Handle search query update from SearchBar and associated results
78113
const handleSearch = async (query: string) => {
79114
setSearchQuery(query);
80115
setSearchClicked(true);
@@ -95,15 +130,15 @@ export default function Index() {
95130
return (
96131
<>
97132
<DynamicMap
98-
center={[mapCenter.lat, mapCenter.lng]}
133+
center={searchCenter}
99134
zoom={zoomLevel}
100135
mapRef={mapRef}
101136
searchQuery={searchQuery}
102137
isSearchClicked={isSearchClicked}
103138
searchResults={searchResults || []}
104139
/>
105140
<SearchBar page={'default'} onSearch={handleSearch} />
106-
<div className="absolute bottom-8 z-10 right-0 left-0 m-auto pointer-events-none">
141+
<div className="absolute bottom-8 z-10 right-0 left-0 m-auto pointer-events-none">
107142
<div className="w-[90%] lg:w-full lg:px-6 mx-auto flex items-center justify-between">
108143
{/* Add Seller Button */}
109144
<div className="pointer-events-auto">

src/components/shared/map/Map.tsx

+28-41
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,10 @@ const Map = ({
5757
isSearchClicked,
5858
searchResults,
5959
}: {
60-
center: LatLngExpression;
60+
center: LatLngExpression | null;
6161
zoom: number;
62-
mapRef: React.MutableRefObject<L.Map | null>; searchQuery: string;
62+
mapRef: React.MutableRefObject<L.Map | null>;
63+
searchQuery: string;
6364
isSearchClicked: boolean;
6465
searchResults: ISeller[];
6566
}) => {
@@ -72,22 +73,21 @@ const Map = ({
7273
popupAnchor: [1, -34],
7374
});
7475

76+
// Define the crosshair icon for the center of the map
77+
const crosshairIcon = new L.Icon({
78+
iconUrl: '/images/icons/crosshair.png',
79+
iconSize: [100, 100],
80+
iconAnchor: [60, 60],
81+
});
82+
7583
const [position, setPosition] = useState<L.LatLng | null>(null);
7684
const [sellers, setSellers] = useState<ISellerWithSettings[]>([]);
77-
const [origin, setOrigin] = useState(center);
7885
const [loading, setLoading] = useState(false);
7986
const [error, setError] = useState<string | null>(null);
8087
const [locationError, setLocationError] = useState(false);
8188
const [isLocationAvailable, setIsLocationAvailable] = useState(false);
8289
const [initialLocationSet, setInitialLocationSet] = useState(false);
8390

84-
// Update origin when center prop changes
85-
useEffect(() => {
86-
if (center) {
87-
setOrigin(center);
88-
}
89-
}, [center]);
90-
9191
useEffect(() => {
9292
if (searchResults.length > 0) {
9393
const sellersWithCoordinates = searchResults.map((seller: any) => {
@@ -173,6 +173,17 @@ const Map = ({
173173
logger.warn('Map instance is not ready yet');
174174
return;
175175
}
176+
// Set and zoom map center to search center if available
177+
if (center){
178+
console.log("initial map center is focus to user center:", center.toString())
179+
mapInstance.setView(center, 8, { animate: true })
180+
} else {
181+
const worldCenter = mapRef.current?.getCenter()
182+
console.log("initial map center focus to world:", worldCenter?.toString())
183+
worldCenter
184+
? mapInstance.setView(worldCenter, 2, { animate: false })
185+
: mapRef.current = mapInstance;
186+
}
176187

177188
const bounds = mapInstance.getBounds();
178189
if (bounds) {
@@ -203,32 +214,10 @@ const Map = ({
203214

204215
logger.info('Fetched additional sellers:', { additionalSellers });
205216

206-
// Filter sellers within the new bounds, checking if coordinates are defined
207-
const filteredSellers = additionalSellers.filter(
208-
(seller) =>
209-
seller.coordinates &&
210-
newBounds.contains([seller.coordinates[0], seller.coordinates[1]])
211-
);
212-
logger.info('Filtered sellers within bounds', { filteredSellers });
213-
214-
// Filter out sellers that are not within the new bounds from the existing sellers
215-
const remainingSellers = sellers.filter(
216-
(seller) =>
217-
seller.coordinates &&
218-
newBounds.contains([seller.coordinates[0], seller.coordinates[1]])
219-
);
220-
logger.info('Remaining sellers within bounds:', { remainingSellers });
221-
222-
// Combine remaining and filtered sellers, remove duplicates, and cap at 36 sellers
223-
const updatedSellers = removeDuplicates([...remainingSellers, ...filteredSellers]);
224-
225-
// Log the combined sellers before slicing
226-
logger.info('Combined sellers (before capping at 36):', { updatedSellers });
227-
228-
setSellers(updatedSellers.slice(0, 36)); // Cap the total sellers to 36
217+
setSellers(additionalSellers); // Cap the total sellers to 36
229218

230219
logger.info('Sellers after capping at 36:', {
231-
updatedSellers: updatedSellers.slice(0, 36),
220+
additionalSellers: additionalSellers,
232221
});
233222

234223
} catch (error) {
@@ -254,10 +243,8 @@ const Map = ({
254243
logger.info(`Location found: ${e.latlng.toString()}`);
255244
setPosition(e.latlng);
256245
setLocationError(false);
257-
if (!initialLocationSet) {
258-
map.setView(e.latlng, zoom, { animate: false });
259-
setInitialLocationSet(true);
260-
setIsLocationAvailable(true);
246+
if (center) {
247+
map.setView(center, zoom, { animate: false });
261248
}
262249
},
263250
locationerror() {
@@ -288,7 +275,7 @@ const Map = ({
288275
}
289276
}, [position, map, initialLocationSet]);
290277

291-
return position === null ? null : <Marker position={position} />;
278+
return center === null ? null : <Marker position={center} icon={crosshairIcon} />;
292279
}
293280

294281
// Define map boundaries
@@ -334,8 +321,8 @@ const Map = ({
334321
</div>
335322
) : (
336323
<MapContainer
337-
center={isLocationAvailable ? origin : [0, 0]}
338-
zoom={isLocationAvailable ? zoom : 2}
324+
center={center ? center : [0,0]}
325+
zoom={center ? zoom : 2}
339326
zoomControl={false}
340327
minZoom={2}
341328
maxZoom={18}

src/components/shared/sidebar/sidebar.tsx

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import styles from './sidebar.module.css';
22

3-
import { useTranslations } from 'next-intl';
3+
import { useTranslations, useLocale } from 'next-intl';
44
import { useTheme } from 'next-themes';
55
import Image from 'next/image';
66
import Link from 'next/link';
@@ -53,9 +53,10 @@ function isLanguageMenuItem(item: MenuItem): item is LanguageMenuItem {
5353
function Sidebar(props: any) {
5454
const t = useTranslations();
5555
const pathname = usePathname();
56+
const local = useLocale();
5657
const router = useRouter();
5758

58-
const { currentUser, autoLoginUser } = useContext(AppContext);
59+
const { currentUser, autoLoginUser, setReload } = useContext(AppContext);
5960
const [dbUserSettings, setDbUserSettings] = useState<IUserSettings | null>(null);
6061
// Initialize state with appropriate types
6162
const [formData, setFormData] = useState<{
@@ -249,6 +250,9 @@ function Sidebar(props: any) {
249250
setIsSaveEnabled(false);
250251
logger.info('User Settings saved successfully:', { data });
251252
toast.success(t('SIDE_NAVIGATION.VALIDATION.SUCCESSFUL_PREFERENCES_SUBMISSION'));
253+
if (pathname === '/' || pathname === `/${local}`) {
254+
setReload(true);
255+
}
252256
}
253257
} catch (error) {
254258
logger.error('Error saving user settings:', { error });

src/constants/types.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ export interface ReviewInt {
6161
image: string;
6262
}
6363

64+
export enum DeviceLocationType {
65+
Automatic = 'auto',
66+
GPS = 'deviceGPS',
67+
SearchCenter = 'searchCenter'
68+
}
69+
6470
// Select specific fields from IUserSettings
6571
export type PartialUserSettings = Pick<IUserSettings, 'user_name' | 'email' | 'phone_number' | 'findme' | 'trust_meter_rating'>;
6672

@@ -72,4 +78,5 @@ export type PartialReview = {
7278
receiver: string;
7379
}
7480

75-
export interface IReviewOutput extends IReviewFeedback, PartialReview {}
81+
export interface IReviewOutput extends IReviewFeedback, PartialReview {}
82+

0 commit comments

Comments
 (0)