From b9d979b2f9e0d99bdb8ba2edae32219154cf744a Mon Sep 17 00:00:00 2001 From: Keith Vhurumuku Date: Fri, 22 May 2026 09:37:56 -0700 Subject: [PATCH 1/3] implemented reverse geo --- nexa/src/app/report/page.tsx | 2 +- nexa/src/hooks/use-geolocation.ts | 42 +++++++++++++++++++++---------- nexa/src/lib/reverse-geocode.ts | 20 +++++++++++++++ 3 files changed, 50 insertions(+), 14 deletions(-) create mode 100644 nexa/src/lib/reverse-geocode.ts diff --git a/nexa/src/app/report/page.tsx b/nexa/src/app/report/page.tsx index 882ce74..0fe16d1 100644 --- a/nexa/src/app/report/page.tsx +++ b/nexa/src/app/report/page.tsx @@ -349,7 +349,7 @@ export default function ReportPage() { onDescriptionChange={setDescription} onAddressChange={handleAddressChange} onDetectLocation={geo.detect} - onLocationChange={geo.setCoordinates} + onLocationChange={geo.movePin} onClassify={handleClassify} /> )} diff --git a/nexa/src/hooks/use-geolocation.ts b/nexa/src/hooks/use-geolocation.ts index d638c7d..1e63ade 100644 --- a/nexa/src/hooks/use-geolocation.ts +++ b/nexa/src/hooks/use-geolocation.ts @@ -1,6 +1,7 @@ "use client"; -import { useState, useCallback } from "react"; +import { useState, useCallback, useRef } from "react"; +import { reverseGeocode } from "@/lib/reverse-geocode"; export function useGeolocation() { const [address, setAddress] = useState(""); @@ -10,6 +11,10 @@ export function useGeolocation() { const [loading, setLoading] = useState(false); const [error, setError] = useState(null); + // Monotonic counter so a slow response from an earlier drag never overwrites + // the result of a more recent one. + const geocodeSeq = useRef(0); + const setCoordinates = useCallback( (lat: number | null, lng: number | null) => { setLatitude(lat); @@ -18,6 +23,16 @@ export function useGeolocation() { [], ); + /** Shared reverse-geocode: updates address to match lat/lng. */ + const refreshAddress = useCallback(async (lat: number, lng: number) => { + const seq = ++geocodeSeq.current; + const name = await reverseGeocode(lat, lng); + if (seq === geocodeSeq.current) { + setAddress(name); + } + }, []); + + /** Browser GPS → set coords + reverse-geocode address. */ const detect = useCallback(() => { if (!navigator.geolocation) { setError("Geolocation is not supported by this browser."); @@ -32,17 +47,7 @@ export function useGeolocation() { const lng = position.coords.longitude; setCoordinates(lat, lng); setAccuracy(position.coords.accuracy); - - try { - const res = await fetch( - `https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&zoom=18&addressdetails=1`, - ); - const data = await res.json(); - if (data.display_name) setAddress(data.display_name); - } catch { - setAddress(`${lat.toFixed(6)}, ${lng.toFixed(6)}`); - } - + await refreshAddress(lat, lng); setLoading(false); }, (err) => { @@ -65,9 +70,19 @@ export function useGeolocation() { maximumAge: 60000, }, ); - }, [setCoordinates]); + }, [setCoordinates, refreshAddress]); + + /** Pin drag → update coords + reverse-geocode to keep address in sync. */ + const movePin = useCallback( + (lat: number, lng: number) => { + setCoordinates(lat, lng); + void refreshAddress(lat, lng); + }, + [setCoordinates, refreshAddress], + ); const reset = useCallback(() => { + geocodeSeq.current += 1; setAddress(""); setCoordinates(null, null); setAccuracy(null); @@ -83,6 +98,7 @@ export function useGeolocation() { loading, error, setCoordinates, + movePin, detect, reset, }; diff --git a/nexa/src/lib/reverse-geocode.ts b/nexa/src/lib/reverse-geocode.ts new file mode 100644 index 0000000..27b74c6 --- /dev/null +++ b/nexa/src/lib/reverse-geocode.ts @@ -0,0 +1,20 @@ +/** + * Reverse-geocode coordinates via Nominatim (same source as Detect Location). + * Returns a display-name string, or a coordinate fallback on failure. + */ +export async function reverseGeocode( + lat: number, + lng: number, +): Promise { + const fallback = `${lat.toFixed(6)}, ${lng.toFixed(6)}`; + try { + const res = await fetch( + `https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&zoom=18&addressdetails=1`, + ); + if (!res.ok) return fallback; + const data = (await res.json()) as { display_name?: string }; + return data.display_name?.trim() || fallback; + } catch { + return fallback; + } +} From c1371a7c2173a30d799192b3543da5ba8b620407 Mon Sep 17 00:00:00 2001 From: Keith Vhurumuku Date: Fri, 22 May 2026 09:40:15 -0700 Subject: [PATCH 2/3] a fix --- nexa/package-lock.json | 93 ++++++++++++++++++++++++++++++++++++++++-- nexa/package.json | 2 +- 2 files changed, 91 insertions(+), 4 deletions(-) diff --git a/nexa/package-lock.json b/nexa/package-lock.json index bf315a2..14897bf 100644 --- a/nexa/package-lock.json +++ b/nexa/package-lock.json @@ -111,6 +111,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -705,6 +706,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -761,7 +763,8 @@ "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.4.1.tgz", "integrity": "sha512-mZ9NzzUSYPOCnxHH1oAHPRzoMFJHY472raDKwXl/+6oPbpdJ7g8LsCN4FSaIIfkiCKHhb3iF/Zqo3NYxaIhU7Q==", "devOptional": true, - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/@electric-sql/pglite-socket": { "version": "0.1.1", @@ -2150,6 +2153,7 @@ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "license": "MIT", + "peer": true, "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", @@ -2392,6 +2396,7 @@ "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", "license": "MIT", + "peer": true, "engines": { "node": "^14.21.3 || >=16" }, @@ -2498,6 +2503,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -3487,6 +3493,70 @@ "node": ">=14.0.0" } }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.8.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.8.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { + "version": "2.8.1", + "dev": true, + "inBundle": true, + "license": "0BSD", + "optional": true + }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz", @@ -3734,6 +3804,7 @@ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -3742,8 +3813,9 @@ "version": "19.2.3", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "devOptional": true, + "dev": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -3827,6 +3899,7 @@ "integrity": "sha512-/Zb/xaIDfxeJnvishjGdcR4jmr7S+bda8PKNhRGdljDM+elXhlvN0FyPSsMnLmJUrVG9aPO6dof80wjMawsASg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.58.2", "@typescript-eslint/types": "8.58.2", @@ -4365,6 +4438,7 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4855,6 +4929,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -6066,6 +6141,7 @@ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -6267,6 +6343,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -6556,6 +6633,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -7452,6 +7530,7 @@ "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.14.tgz", "integrity": "sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==", "license": "MIT", + "peer": true, "engines": { "node": ">=16.9.0" } @@ -8412,7 +8491,8 @@ "version": "1.9.4", "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", - "license": "BSD-2-Clause" + "license": "BSD-2-Clause", + "peer": true }, "node_modules/levn": { "version": "0.4.1", @@ -9760,6 +9840,7 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz", "integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==", "license": "MIT", + "peer": true, "dependencies": { "pg-connection-string": "^2.12.0", "pg-pool": "^3.13.0", @@ -10096,6 +10177,7 @@ "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@prisma/config": "7.7.0", "@prisma/dev": "0.24.3", @@ -10321,6 +10403,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -10330,6 +10413,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -11544,6 +11628,7 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -11817,6 +11902,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -12414,6 +12500,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/nexa/package.json b/nexa/package.json index b85a041..2ed5765 100644 --- a/nexa/package.json +++ b/nexa/package.json @@ -4,7 +4,7 @@ "private": true, "scripts": { "dev": "next dev", - "build": "prisma generate && ([ -n \"$DATABASE_URL\" ] && prisma migrate deploy || true) && next build", + "build": "prisma generate && ([ -n \"$DATABASE_URL\" ] && prisma migrate deploy || true) && next build --webpack", "start": "next start", "lint": "eslint", "format": "prettier --write .", From cce14810e2ee52fbfbf6651f3c8a7fba05c0c7ba Mon Sep 17 00:00:00 2001 From: Keith Vhurumuku Date: Fri, 22 May 2026 09:44:47 -0700 Subject: [PATCH 3/3] removed webpack --- nexa/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nexa/package.json b/nexa/package.json index 2ed5765..b85a041 100644 --- a/nexa/package.json +++ b/nexa/package.json @@ -4,7 +4,7 @@ "private": true, "scripts": { "dev": "next dev", - "build": "prisma generate && ([ -n \"$DATABASE_URL\" ] && prisma migrate deploy || true) && next build --webpack", + "build": "prisma generate && ([ -n \"$DATABASE_URL\" ] && prisma migrate deploy || true) && next build", "start": "next start", "lint": "eslint", "format": "prettier --write .",