Skip to content

Add fullscreen 3D Map page with geolocation + IP fallback#2

Open
tejpratap46 wants to merge 1 commit into
mainfrom
codex/add-full-screen-3d-map-page
Open

Add fullscreen 3D Map page with geolocation + IP fallback#2
tejpratap46 wants to merge 1 commit into
mainfrom
codex/add-full-screen-3d-map-page

Conversation

@tejpratap46

Copy link
Copy Markdown
Owner

Motivation

  • Provide a full-screen pitched 3D map view so users can see a MapLibre/OpenFreeMap map centered on their location.
  • Prefer high-accuracy browser geolocation and fall back to an IP-based lookup when permission or geolocation is unavailable.
  • Surface a simple status/coordinates overlay and make the page reachable from the command palette.

Description

  • Add a new page pages/map.tsx that loads MapLibre from CDN, initializes a pitched/3D map (pitch, bearing, antialias), adds navigation/geolocate/fullscreen controls, and places a marker at the resolved center.
  • Implement browser geolocation in the page (getBrowserLocation) with an async fallback to GET /api/ip-lookup (getIpLocation) and a final default center when both fail.
  • Improve pages/api/ip-lookup.ts to read common forwarding headers (x-forwarded-for, cf-connecting-ip), handle missing loc from the IP provider, and return a safe fallbackLocation.
  • Register the new route in the command palette by updating components/search/Actions.tsx to include an entry for /map.

Testing

  • Ran npm run lint, which completed with non-blocking warnings.
  • Ran npm run build, which completed successfully and produced an optimized build.
  • Started the production server and performed a smoke test by curling /map, which returned 200 OK and the expected page HTML including the 3D Map Wake Lock title and MapLibre stylesheet reference.

Codex Task

@vercel

vercel Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
pwa-wake-lock Ready Ready Preview, Comment Jun 4, 2026 4:52pm

@netlify

netlify Bot commented Jun 4, 2026

Copy link
Copy Markdown

Deploy Preview for pwawake failed.

Name Link
🔨 Latest commit c8222cd
🔍 Latest deploy log https://app.netlify.com/projects/pwawake/deploys/6a21ad181c3a8b00085e428d

@amazon-q-developer amazon-q-developer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

This PR adds a full-screen 3D map page with geolocation and IP fallback, improving the application's feature set. The implementation includes proper fallback logic and error handling in the frontend.

Critical Issues Found

Security Vulnerability: The API token for ipinfo.io is hardcoded in the source code, which exposes credentials and violates security best practices. This must be moved to environment variables before merging.

Missing Error Handling: The API handler lacks try-catch blocks to handle fetch failures, network errors, and JSON parsing errors, which will cause unhandled promise rejections and crash the handler.

Required Actions

  1. Move the ipinfo.io API token to an environment variable (e.g., process.env.IPINFO_TOKEN)
  2. Wrap the API handler logic in a try-catch block to gracefully handle errors
  3. Add the environment variable to your deployment configuration

Once these security and error handling issues are resolved, the PR will be ready to merge.


You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.

Comment thread pages/api/ip-lookup.ts
const ipResponse = await fetch(`https://ipinfo.io/${clientIP}/json?token=ef8461623ce04a`)
const clientIp = getClientIp(req)
const ipResponse = await fetch(
`https://ipinfo.io/${clientIp}/json?token=ef8461623ce04a`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 Security Vulnerability: Hardcoded API token exposes credentials in source code. Move the ipinfo.io API token to an environment variable to prevent unauthorized access and token theft.1

Suggested change
`https://ipinfo.io/${clientIp}/json?token=ef8461623ce04a`,
`

Footnotes

  1. CWE-798: Use of Hard-coded Credentials - https://cwe.mitre.org/data/definitions/798.html

Comment thread pages/api/ip-lookup.ts
Comment on lines +33 to 36
const ipResponse = await fetch(
`https://ipinfo.io/${clientIp}/json?token=ef8461623ce04a`,
)
const apiResponse: ApiResponse = await ipResponse.json()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add error handling for fetch failures. Network errors or API downtime will crash the handler without proper error handling.1

Suggested change
const ipResponse = await fetch(
`https://ipinfo.io/${clientIp}/json?token=ef8461623ce04a`,
)
const apiResponse: ApiResponse = await ipResponse.json()
try {
const ipResponse = await fetch(
`
)
if (!ipResponse.ok) {
res.status(200).json(fallbackLocation)
return
}
const apiResponse: ApiResponse = await ipResponse.json()

Footnotes

  1. CWE-755: Improper Handling of Exceptional Conditions - https://cwe.mitre.org/data/definitions/755.html

Comment thread pages/api/ip-lookup.ts
Comment on lines 28 to 49
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
res: NextApiResponse<Data>,
) {
const clientIP = req.headers['cf-connecting-ip']
const ipResponse = await fetch(`https://ipinfo.io/${clientIP}/json?token=ef8461623ce04a`)
const clientIp = getClientIp(req)
const ipResponse = await fetch(
`https://ipinfo.io/${clientIp}/json?token=ef8461623ce04a`,
)
const apiResponse: ApiResponse = await ipResponse.json()

const [lat, long] = apiResponse.loc.split(",")
if (!apiResponse.loc) {
res.status(200).json(fallbackLocation)
return
}

const [lat, long] = apiResponse.loc.split(',')
const data: Data = {
latitude: parseFloat(lat),
longitude: parseFloat(long)
longitude: parseFloat(long),
}

res.status(200).json(data)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add error handling for JSON parsing and API response errors. Without a try-catch block, fetch failures, network errors, and JSON parsing errors will cause unhandled promise rejections and crash the API handler.1

Suggested change
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
res: NextApiResponse<Data>,
) {
const clientIP = req.headers['cf-connecting-ip']
const ipResponse = await fetch(`https://ipinfo.io/${clientIP}/json?token=ef8461623ce04a`)
const clientIp = getClientIp(req)
const ipResponse = await fetch(
`https://ipinfo.io/${clientIp}/json?token=ef8461623ce04a`,
)
const apiResponse: ApiResponse = await ipResponse.json()
const [lat, long] = apiResponse.loc.split(",")
if (!apiResponse.loc) {
res.status(200).json(fallbackLocation)
return
}
const [lat, long] = apiResponse.loc.split(',')
const data: Data = {
latitude: parseFloat(lat),
longitude: parseFloat(long)
longitude: parseFloat(long),
}
res.status(200).json(data)
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Data>,
) {
try {
const clientIp = getClientIp(req)
const ipResponse = await fetch(
`
)
if (!ipResponse.ok) {
res.status(200).json(fallbackLocation)
return
}
const apiResponse: ApiResponse = await ipResponse.json()
if (!apiResponse.loc) {
res.status(200).json(fallbackLocation)
return
}
const [lat, long] = apiResponse.loc.split(',')
const data: Data = {
latitude: parseFloat(lat),
longitude: parseFloat(long),
}
res.status(200).json(data)
} catch (error) {
res.status(200).json(fallbackLocation)
}
}

Footnotes

  1. CWE-755: Improper Handling of Exceptional Conditions - https://cwe.mitre.org/data/definitions/755.html

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new 3D Map page and search action, alongside refactoring the IP lookup API to improve client IP detection and fallback handling. The review feedback highlights two important issues: a hardcoded API token in the IP lookup endpoint that should be moved to an environment variable, and a React useEffect dependency issue in the map component that causes the map to be unnecessarily destroyed and recreated on location updates.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread pages/api/ip-lookup.ts
Comment on lines +33 to +35
const ipResponse = await fetch(
`https://ipinfo.io/${clientIp}/json?token=ef8461623ce04a`,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The IPInfo API token is hardcoded in the fetch URL. Hardcoding API keys or tokens in source code is a security risk and makes it difficult to rotate keys. Please move this token to an environment variable (e.g., process.env.IPINFO_TOKEN) and access it securely.

Suggested change
const ipResponse = await fetch(
`https://ipinfo.io/${clientIp}/json?token=ef8461623ce04a`,
)
const token = process.env.IPINFO_TOKEN || 'ef8461623ce04a'
const ipResponse = await fetch(
`https://ipinfo.io/${clientIp}/json?token=${token}`,
)

Comment thread pages/map.tsx
Comment on lines +157 to +214
useEffect(() => {
if (
!isMapLibreReady ||
!location ||
!mapContainerRef.current ||
mapRef.current
) {
return
}

const maplibregl = window.maplibregl

if (!maplibregl) {
setStatus('Map library failed to load')
return
}

const center: [number, number] = [
location.coordinates.longitude,
location.coordinates.latitude,
]
const map = new maplibregl.Map({
container: mapContainerRef.current,
style: MAP_STYLE,
center,
zoom: 16,
pitch: 60,
bearing: -20,
antialias: true,
attributionControl: { compact: true },
})

map.addControl(
new maplibregl.NavigationControl({ visualizePitch: true }),
'top-right',
)
map.addControl(
new maplibregl.GeolocateControl({
positionOptions: browserLocationOptions,
trackUserLocation: true,
showUserHeading: true,
}),
'top-right',
)
map.addControl(new maplibregl.FullscreenControl(), 'top-right')

markerRef.current = new maplibregl.Marker({ color: '#38bdf8' })
.setLngLat(center)
.addTo(map)
mapRef.current = map

return () => {
markerRef.current?.remove()
map.remove()
markerRef.current = null
mapRef.current = null
}
}, [isMapLibreReady, location])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The useEffect hook that initializes the map has location in its dependency array. In React, even if you return early using mapRef.current, the cleanup function from the previous execution of the effect is run before the next execution. This means that whenever location changes, the map is destroyed (map.remove()) and recreated from scratch, causing a poor user experience and unnecessary API/tile requests.

To fix this, you should separate the map initialization from location updates. You can track whether the location has been loaded using a boolean flag (e.g., hasLocation = location !== null) and use that as a dependency instead of the location object itself. This ensures the map is initialized only once when the location first becomes available, and subsequent location updates are handled smoothly by the other useEffect hook.

	const hasLocation = location !== null
	const locationRef = useRef(location)
	useEffect(() => {
		locationRef.current = location
	}, [location])

	useEffect(() => {
		if (
			!isMapLibreReady ||
			!hasLocation ||
			!mapContainerRef.current ||
			mapRef.current
		) {
			return
		}

		const maplibregl = window.maplibregl

		if (!maplibregl || !locationRef.current) {
			setStatus('Map library failed to load')
			return
		}

		const center: [number, number] = [
			locationRef.current.coordinates.longitude,
			locationRef.current.coordinates.latitude,
		]
		const map = new maplibregl.Map({
			container: mapContainerRef.current,
			style: MAP_STYLE,
			center,
			zoom: 16,
			pitch: 60,
			bearing: -20,
			antialias: true,
			attributionControl: { compact: true },
		})

		map.addControl(
			new maplibregl.NavigationControl({ visualizePitch: true }),
			'top-right',
		)
		map.addControl(
			new maplibregl.GeolocateControl({
				positionOptions: browserLocationOptions,
				trackUserLocation: true,
				showUserHeading: true,
			}),
			'top-right',
		)
		map.addControl(new maplibregl.FullscreenControl(), 'top-right')

		markerRef.current = new maplibregl.Marker({ color: '#38bdf8' })
			.setLngLat(center)
			.addTo(map)
		mapRef.current = map

		return () => {
			markerRef.current?.remove()
			map.remove()
			markerRef.current = null
			mapRef.current = null
		}
	}, [isMapLibreReady, hasLocation])

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant