Skip to content

Comments

Add precise location sharing option for chapter map#2644

Merged
kasya merged 25 commits intoOWASP:mainfrom
anurag2787:location-sharing
Dec 14, 2025
Merged

Add precise location sharing option for chapter map#2644
kasya merged 25 commits intoOWASP:mainfrom
anurag2787:location-sharing

Conversation

@anurag2787
Copy link
Contributor

@anurag2787 anurag2787 commented Nov 15, 2025

Resolves #2583

Proposed change

This PR adds an opt-in precise location sharing option for the Chapter Map.

  • When a user visits /chapter, they can choose to share their current location.
  • If the user allows location sharing, the map updates to show the nearest chapter based on the user’s coordinates.
  • If the user denies or skips location sharing, the map behaves as before and uses the default chapter geoLocation data.

Checklist

  • I've read and followed the contributing guidelines.
  • I've run make check-test locally; all checks and tests passed.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 15, 2025

Summary by CodeRabbit

  • New Features
    • Enhanced chapter map with optional location sharing: users can share/reset their location, see their position on the map, and use a control to toggle sharing.
    • When enabled, chapters are sorted by proximity and the map centers/zooms to the nearest chapter for quicker discovery.

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

Adds opt-in browser geolocation: new geolocation utilities, ChapterMap and ChapterMapWrapper accept userLocation and onShareLocation, chapters can be sorted by proximity and map recenters when enabled, and the Chapters page enables the location-sharing UI.

Changes

Cohort / File(s) Summary
Geolocation utilities
frontend/src/utils/geolocationUtils.ts
New module exporting UserLocation type, calculateDistance (haversine), getUserLocationFromBrowser() (Promise), helpers to extract coordinates from chapters, and sortChaptersByDistance() that annotates and sorts chapters by distance.
Map component
frontend/src/components/ChapterMap.tsx
Added optional props `userLocation?: UserLocation
Map wrapper component
frontend/src/components/ChapterMapWrapper.tsx
New ChapterMapWrapperProps with showLocationSharing?: boolean; introduces userLocation and sortedData state, handleShareLocation to fetch browser location and sort chapters via utilities, toggles/reset sharing; falls back to original rendering when disabled.
Page integration
frontend/src/app/chapters/page.tsx
Passes showLocationSharing={true} to ChapterMapWrapper, enabling the location-sharing UI on the Chapters page.
Manifest
package.json
Manifest file modified (entry present in diff; specific changes not shown).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20–30 minutes

  • Pay attention to: haversine implementation and units, coordinate extraction/filtering for null/invalid values, browser geolocation error/permission handling, and prop typings/compatibility between ChapterMapWrapper, ChapterMap, and callers.

Possibly related PRs

Suggested reviewers

  • arkid15r

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and concisely summarizes the main change: adding a location sharing option for the chapter map. It matches the primary objective described in the linked issue.
Description check ✅ Passed The description is directly related to the changeset, explaining the opt-in location sharing feature, user behavior, and fallback behavior. It covers the main objectives clearly.
Linked Issues check ✅ Passed The PR implements the core requirements from issue #2583: opt-in geolocation sharing for the chapter map, presenting user choice on /chapter, using coordinates to find nearest chapter, and maintaining existing behavior on denial.
Out of Scope Changes check ✅ Passed All code changes are scoped to implementing location sharing: new geolocation utilities, ChapterMapWrapper state management, ChapterMap UI updates, and integration on the chapters page. No unrelated changes detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@anurag2787 anurag2787 marked this pull request as ready for review November 15, 2025 10:07
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (5)
frontend/src/utils/geolocationUtils.ts (3)

31-59: Ensure getUserLocationFromBrowser stays client‑only and consider explicit “denied” handling

The geolocation flow and error handling are reasonable, but this helper assumes a browser environment (navigator.geolocation). Make sure it’s only imported and called from client components, and consider whether you want to distinguish “denied / failed” from “not requested yet” in the UI instead of always resolving null with only a console warning.


61-80: Coordinate extraction is robust but could be tightened and deduplicated

The multiple fallbacks for lat/lng across _geoloc, geoLocation, and location are helpful for handling legacy shapes, but:

  • The repeated casts to Record<string, unknown> could be wrapped in a small helper to avoid duplication.
  • If this is only intended for Chapter objects today, typing the parameter as Chapter (or a narrower structural type) would give you better safety than Record<string, unknown>.

85-103: Distance sorting works, but the typing and filter could be more precise

The map/filter/sort pipeline correctly excludes chapters without valid coordinates and orders the rest by distance. Two type‑level improvements would reduce assertions and make the return type easier to use:

  • Narrow the filter with a type predicate instead of item !== null plus as/!.
  • Align the generic shape with actual usage (e.g., Chapter[] or a generic <T> that returns Array<T & { distance: number }>), so you don’t lose the concrete type when calling this from ChapterMapWrapper.

Example pattern:

.filter((item): item is { /* your shape */ } => item !== null)

This will also help keep sortedData’s type in sync with what’s returned.

frontend/src/components/ChapterMapWrapper.tsx (2)

57-100: Avoid leaking showLocationSharing prop into ChapterMap and align sortedData typing

Two related refinements here:

  1. ChapterMap likely doesn’t declare a showLocationSharing prop, but {...props} forwards it anyway. This can cause type errors if ChapterMap has a strict props interface and also blurs the wrapper’s responsibility boundary.

  2. sortedData is typed as Chapter[] | null, while sortChaptersByDistance currently returns objects augmented with a distance field. Even though this extra field is harmless at runtime, the mismatch can force casts or assertions elsewhere.

You can address both by stripping the wrapper‑only prop and tightening the types:

-const ChapterMapWrapper: React.FC<ChapterMapWrapperProps> = (props) => {
-  const [userLocation, setUserLocation] = useState<UserLocation | null>(null)
-  const [isLoadingLocation, setIsLoadingLocation] = useState(false)
-  const [sortedData, setSortedData] = useState<Chapter[] | null>(null)
+const ChapterMapWrapper: React.FC<ChapterMapWrapperProps> = (props) => {
+  const { showLocationSharing, ...mapProps } = props
+  const [userLocation, setUserLocation] = useState<UserLocation | null>(null)
+  const [isLoadingLocation, setIsLoadingLocation] = useState(false)
+  const [sortedData, setSortedData] = useState<Chapter[] | null>(null) // or Chapter & { distance: number }[]
@@
-  const enableLocationSharing = props.showLocationSharing === true
+  const enableLocationSharing = showLocationSharing === true
@@
-        setSortedData(sortChaptersByDistance(props.geoLocData, location))
+        setSortedData(sortChaptersByDistance(mapProps.geoLocData, location))
@@
-  const mapData = sortedData ?? props.geoLocData
+  const mapData = sortedData ?? mapProps.geoLocData
@@
-    <div className="space-y-4">
+    <div className="space-y-4">
@@
-      <ChapterMap {...props} geoLocData={mapData} />
+      <ChapterMap {...mapProps} geoLocData={mapData} />

Adjusting sortChaptersByDistance’s return type as suggested in geolocationUtils.ts will let you update sortedData’s type accordingly.


60-97: Location‑sharing UI and accessibility are in good shape; consider a failure state message

The button’s aria-label, title, and isLoading/disabled flags make the interaction accessible, and the status text clearly indicates whether a location filter is active. One minor UX enhancement would be to surface a brief inline message when geolocation fails or is denied (instead of silently returning to the default “Find chapters near you” text), so users understand why nothing changed after clicking.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c6451c5 and 27b4a44.

📒 Files selected for processing (3)
  • frontend/src/app/chapters/page.tsx (1 hunks)
  • frontend/src/components/ChapterMapWrapper.tsx (1 hunks)
  • frontend/src/utils/geolocationUtils.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-13T11:29:25.245Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: frontend/src/app/mentorship/programs/page.tsx:59-61
Timestamp: 2025-07-13T11:29:25.245Z
Learning: In Next.js 13+ app router, components with the 'use client' directive run entirely on the client side and don't require window object existence checks or SSR hydration considerations. Direct access to window.location and other browser APIs is safe in client components.

Applied to files:

  • frontend/src/components/ChapterMapWrapper.tsx
🧬 Code graph analysis (1)
frontend/src/components/ChapterMapWrapper.tsx (2)
frontend/src/types/chapter.ts (1)
  • Chapter (4-21)
frontend/src/utils/geolocationUtils.ts (3)
  • UserLocation (1-6)
  • getUserLocationFromBrowser (31-59)
  • sortChaptersByDistance (85-103)
🔇 Additional comments (3)
frontend/src/app/chapters/page.tsx (1)

90-102: Prop wiring for enabling location sharing looks correct

Passing showLocationSharing={true} here cleanly enables the new opt‑in location UI for the chapters page without affecting behavior when no chapters are loaded.

frontend/src/utils/geolocationUtils.ts (1)

13-29: Haversine distance implementation looks mathematically sound

The calculateDistance implementation (degree→radian conversion, a term, and R = 6371 km) is correct for a haversine distance in kilometers and handles edge cases (identical points) as expected.

frontend/src/components/ChapterMapWrapper.tsx (1)

22-57: Location‑sharing flow and toggle behavior are logically sound

The wrapper cleanly gates the feature behind showLocationSharing, toggles the filter on repeated clicks (set/unset userLocation and sortedData), and correctly guards loading state around getUserLocationFromBrowser with a finally block. This matches the opt‑in requirement without altering default behavior when the feature is disabled.

@anurag2787
Copy link
Contributor Author

Could you please review this? Let me know if its looks fine so that i can proceed with test part

Copy link
Collaborator

@kasya kasya left a comment

Choose a reason for hiding this comment

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

@anurag2787 this works great! Please proceed with working on tests 👍🏼 This looks good so far! Feel free to move this back into drafts if needed.

I do have a couple of questions!
When I use the button the map is pretty zoomed out 🤔 For example, it shows the full state I'm in. Is there a way to zoom in more or is it the best we can get? Also a few comments down below ⬇️

Comment on lines 40 to 43
// # NOSONAR Geolocation permission is necessary for "Find chapters near you" feature.
// User explicitly opts-in via button click. Location stays client-side only.
// eslint-disable-next-line no-unsanitized/property
navigator.geolocation.getCurrentPosition(
Copy link
Collaborator

Choose a reason for hiding this comment

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

I can see Sonar still gets triggered on this for some reason 🤔

@anurag2787 anurag2787 marked this pull request as draft November 16, 2025 14:45
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (4)
frontend/src/components/ChapterMap.tsx (1)

109-148: Consider extracting duplicate logic.

The userLocation block (lines 109-128) and showLocal block (lines 129-148) share nearly identical logic with only two differences: maxZoom value (12 vs 7) and the triggering condition. This duplication increases maintenance burden.

Consider extracting the shared logic:

const applyLocalZoom = (zoom: number) => {
  const maxNearestChapters = 5
  const localChapters = validGeoLocData.slice(0, maxNearestChapters)
  const localBounds = L.latLngBounds(
    localChapters.map((chapter) => [
      chapter._geoloc?.lat ?? chapter.geoLocation?.lat,
      chapter._geoloc?.lng ?? chapter.geoLocation?.lng,
    ])
  )
  const nearestChapter = validGeoLocData[0]
  map.setView(
    [
      nearestChapter._geoloc?.lat ?? nearestChapter.geoLocation?.lat,
      nearestChapter._geoloc?.lng ?? nearestChapter.geoLocation?.lng,
    ],
    zoom
  )
  map.fitBounds(localBounds, { maxZoom: zoom })
}

if (userLocation && validGeoLocData.length > 0) {
  applyLocalZoom(12)
} else if (showLocal && validGeoLocData.length > 0) {
  applyLocalZoom(7)
}
frontend/src/utils/geolocationUtils.ts (2)

31-60: Consider enabling high accuracy for "precise location" feature.

The function uses enableHighAccuracy: false which may provide less accurate results. Since the PR title and feature description specifically mention "precise location sharing," consider setting this to true for better accuracy, acknowledging it may take longer and use more battery.

If you want to prioritize accuracy:

 {
-  enableHighAccuracy: false,
+  enableHighAccuracy: true,
   timeout: 10000,
   maximumAge: 0,
 }

86-104: Consider stronger typing for sortChaptersByDistance.

The function uses Record<string, unknown>[] instead of the more specific Chapter[] type, which loses type safety and IDE support. Since the function is specifically designed to sort chapters and ChapterMapWrapper passes props.geoLocData (typed as Chapter[]), consider using the Chapter type directly.

import type { Chapter } from 'types/chapter'

export const sortChaptersByDistance = (
  chapters: Chapter[],
  userLocation: UserLocation
): Array<Chapter & { distance: number }> => {
  return chapters
    .map((chapter) => {
      const { lat, lng } = extractChapterCoordinates(chapter as unknown as Record<string, unknown>)
      
      if (typeof lat !== 'number' || typeof lng !== 'number') return null
      
      const distance = calculateDistance(userLocation.latitude, userLocation.longitude, lat, lng)
      
      return { ...chapter, distance }
    })
    .filter((item): item is Chapter & { distance: number } => item !== null)
    .sort((a, b) => a.distance - b.distance)
}

This eliminates the non-null assertions and provides better type safety.

frontend/src/components/ChapterMapWrapper.tsx (1)

50-52: Consider providing user feedback on location errors.

The error handler logs to console but doesn't provide user feedback when location detection fails. Users who grant permission but encounter errors (timeout, position unavailable, etc.) won't know what happened.

Consider adding error state and displaying a user-friendly message:

const [error, setError] = useState<string | null>(null)

// In handleShareLocation:
try {
  setError(null)
  const location = await getUserLocationFromBrowser()
  if (!location) {
    setError('Unable to determine your location. Please try again.')
    return
  }
  // ... rest of logic
} catch (error) {
  console.error('Error detecting location:', error)
  setError('Failed to access location. Please check your browser permissions.')
}

// In render:
{error && (
  <div className="text-xs text-red-600 dark:text-red-400">{error}</div>
)}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 47e56a2 and bd0111b.

📒 Files selected for processing (3)
  • frontend/src/components/ChapterMap.tsx (4 hunks)
  • frontend/src/components/ChapterMapWrapper.tsx (1 hunks)
  • frontend/src/utils/geolocationUtils.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
frontend/src/components/ChapterMap.tsx (2)
frontend/src/types/chapter.ts (1)
  • Chapter (4-21)
frontend/src/utils/geolocationUtils.ts (1)
  • UserLocation (1-6)
frontend/src/components/ChapterMapWrapper.tsx (2)
frontend/src/types/chapter.ts (1)
  • Chapter (4-21)
frontend/src/utils/geolocationUtils.ts (3)
  • UserLocation (1-6)
  • getUserLocationFromBrowser (31-60)
  • sortChaptersByDistance (86-104)
🔇 Additional comments (7)
frontend/src/components/ChapterMap.tsx (1)

5-5: LGTM!

The UserLocation type import and optional prop addition are properly implemented with correct TypeScript typing.

Also applies to: 16-16, 21-21

frontend/src/utils/geolocationUtils.ts (2)

1-6: LGTM!

Clean interface definition with appropriate required and optional fields.


13-29: LGTM!

The Haversine formula implementation is correct for calculating great-circle distance between two points on a sphere.

frontend/src/components/ChapterMapWrapper.tsx (4)

1-20: LGTM!

Clean imports and proper TypeScript typing for the component props and function component.

Also applies to: 22-22


23-31: LGTM!

Clean state initialization and early return pattern when location sharing is disabled.


77-97: LGTM! Clean UI feedback states.

The location status display provides clear feedback for both active and inactive states, with appropriate styling and accessibility attributes.

Note: I see from past review comments that displaying coordinates instead of city names was already discussed (would require reverse geocoding). The current coordinate display is a reasonable approach.

Based on learnings


58-58: LGTM! Proper data flow to ChapterMap.

The component correctly selects between sorted and original data, and properly passes both the data and userLocation to ChapterMap for rendering.

Also applies to: 100-100

@anurag2787
Copy link
Contributor Author

@kasya @arkid15r I tried adding // NOSONAR and also added a proper justification comment explaining the geolocation usage but the sonar warning still isn’t being resolved Could you please help me into this?

@anurag2787 anurag2787 marked this pull request as ready for review November 18, 2025 10:57
@anurag2787 anurag2787 requested a review from kasya November 18, 2025 10:57
@kasya
Copy link
Collaborator

kasya commented Nov 19, 2025

@kasya @arkid15r I tried adding // NOSONAR and also added a proper justification comment explaining the geolocation usage but the sonar warning still isn’t being resolved Could you please help me into this?

@anurag2787 It seems that Sonar is totally against it and only suggests to use user input and go from there :( I'm not sure if we can override this.

@arkid15r is there anything in the SQ admin that we could disable for this approach to work?

Copy link
Collaborator

@arkid15r arkid15r left a comment

Choose a reason for hiding this comment

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

Shouldn't the new button be part of the map and available after a click on "Click to interact with map" button only? I believe it'd be cleaner.

We could just add a small button consistent with the map style (with a good tooltip). Instead of showing coordinates values we could just add a pin (the red one) indicating user's location on the map.

@kasya wdyt?

Screenshot 2025-11-18 at 6 26 16 PM

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
frontend/src/utils/geolocationUtils.ts (2)

31-64: Harden browser-only usage and align // NOSONAR with the geolocation line

The overall flow (opt‑in, local‑only usage, timeout, graceful error handling) looks solid. Two small tweaks would tighten things up and should help with Sonar:

  • Guard against non‑browser environments (typeof navigator === 'undefined') so this safely returns null instead of throwing if ever called outside the browser.
  • Move // NOSONAR onto the line where navigator.geolocation is actually read, since Sonar only suppresses issues on the same line.

Suggested refactor:

-export const getUserLocationFromBrowser = (): Promise<UserLocation | null> => {
-  return new Promise((resolve) => {
-    if (!navigator.geolocation) {
-      // eslint-disable-next-line no-console
-      console.warn('Geolocation API not supported')
-      resolve(null)
-      return
-    }
+export const getUserLocationFromBrowser = (): Promise<UserLocation | null> => {
+  return new Promise((resolve) => {
+    if (typeof navigator === 'undefined' || !navigator.geolocation) {
+      // eslint-disable-next-line no-console
+      console.warn('Geolocation API not supported')
+      resolve(null)
+      return
+    }
@@
-    navigator.geolocation.getCurrentPosition(
+    const geolocation = navigator.geolocation // NOSONAR - geolocation used only after explicit user opt-in; data stays client-side (see comment above).
+    geolocation.getCurrentPosition(
       (position) => {
         resolve({
           latitude: position.coords.latitude,
           longitude: position.coords.longitude,
         })
@@
-      {
-        enableHighAccuracy: false,
-        timeout: 10000,
-        maximumAge: 0,
-      }
-    )
-  }) // NOSONAR
+      {
+        enableHighAccuracy: false,
+        timeout: 10000,
+        maximumAge: 0,
+      }
+    )
+  })
 }

Please re-run the Sonar check after this change to confirm the warning is actually suppressed on the targeted line.


80-101: Consider a small typing cleanup in sortChaptersByDistance

Logic is correct, but you can make the API more ergonomic and type‑safe by preserving the caller’s chapter type and avoiding the null + type assertion combo:

  • Make the function generic over the chapter type.
  • Use a type‑predicate in the filter so you don’t need a! and the final cast.

Example refactor:

-export const sortChaptersByDistance = (
-  chapters: Record<string, unknown>[],
-  userLocation: UserLocation
-): Array<Record<string, unknown> & { distance: number }> => {
-  return chapters
-    .map((chapter) => {
-      const { lat, lng } = extractChapterCoordinates(chapter)
-
-      if (typeof lat !== 'number' || typeof lng !== 'number') return null
-
-      const distance = calculateDistance(userLocation.latitude, userLocation.longitude, lat, lng)
-
-      return { ...chapter, distance }
-    })
-    .filter((item) => item !== null) // remove invalid ones
-    .sort((a, b) => a!.distance - b!.distance) as Array<
-    Record<string, unknown> & { distance: number }
-  >
-}
+export const sortChaptersByDistance = <T extends Record<string, unknown>>(
+  chapters: T[],
+  userLocation: UserLocation
+): Array<T & { distance: number }> => {
+  return chapters
+    .map((chapter) => {
+      const { lat, lng } = extractChapterCoordinates(chapter)
+
+      if (typeof lat !== 'number' || typeof lng !== 'number') return null
+
+      const distance = calculateDistance(userLocation.latitude, userLocation.longitude, lat, lng)
+
+      return { ...chapter, distance }
+    })
+    .filter((item): item is T & { distance: number } => item !== null) // remove invalid ones
+    .sort((a, b) => a.distance - b.distance)
+}

This keeps the original chapter shape at call sites while maintaining the same runtime behavior.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 81d3bd7 and b613afe.

📒 Files selected for processing (1)
  • frontend/src/utils/geolocationUtils.ts (1 hunks)
🔇 Additional comments (3)
frontend/src/utils/geolocationUtils.ts (3)

1-11: Location and chapter coordinate shapes look good

The UserLocation and ChapterCoordinates structures match how this module uses them (lat/lng plus optional metadata), so there are no functional or safety concerns here from my side.


13-29: Haversine distance calculation is implemented correctly

The formula, radian conversions, and choice of radius (Earth ≈ 6371 km) are all correct for computing great‑circle distance in kilometers. No changes needed.


66-78: Coordinate extraction matches current chapter data model

This helper now cleanly reflects the actual _geoloc/geoLocation shape (lat/lng only) and defers validity checks to the caller. That’s simple and effective; no further changes needed here.

@github-actions
Copy link

github-actions bot commented Dec 9, 2025

The linked issue must be assigned to the PR author.

@sonarqubecloud
Copy link

sonarqubecloud bot commented Dec 9, 2025

@kasya kasya reopened this Dec 9, 2025
Copy link
Collaborator

@kasya kasya left a comment

Choose a reason for hiding this comment

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

@anurag2787 please address Sonar issues that were introduced

As for your attached video in the issue - can we move the button to the bottom left corner and make it similar style as zoom buttons on the map?
This should be just a button with a target icon, no words. On hover we can say something like Share your current location to find nearby chapters

P.S. We reopened this, but in the future please do not abandon PRs like that. We have strict deadlines on issues, and it's not fair to other contributors if we allow others to just put PR in draft and keep it for weeks. If you need to take pause due to personal issues - just communicate it to us in advance.

@anurag2787
Copy link
Contributor Author

Thanks for reopening the PR. I’ll update it soon according to the review and will keep this in mind from next time. Thanks!

@anurag2787 anurag2787 marked this pull request as ready for review December 9, 2025 20:12
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
frontend/src/components/ChapterMap.tsx (1)

138-175: Centering on nearest chapter works; fix off‑by‑one in showLocal as previously noted

The new userLocation branch correctly:

  • Uses the (distance‑sorted) geoLocData to pick nearestChapter = validGeoLocData[0].
  • Fits bounds around up to maxNearestChapters chapters via slice(0, maxNearestChapters).

However, the showLocal branch below still uses slice(0, maxNearestChapters - 1), so you only get 4 chapters when maxNearestChapters = 5. This is the same off‑by‑one issue flagged in earlier reviews and is still present.

You can align both branches with:

-    } else if (showLocal && validGeoLocData.length > 0) {
-      const maxNearestChapters = 5
-      const localChapters = validGeoLocData.slice(0, maxNearestChapters - 1)
+    } else if (showLocal && validGeoLocData.length > 0) {
+      const maxNearestChapters = 5
+      const localChapters = validGeoLocData.slice(0, maxNearestChapters)

This ensures you consistently use the intended number of nearby chapters for bounds in both modes.

frontend/src/components/ChapterMapWrapper.tsx (1)

29-48: Fix distance-field destructuring in sorted data mapping

sortChaptersByDistance augments chapters with a distance property, but this code destructures _distance, which doesn’t exist. As a result, distance remains on the objects passed to ChapterMap, which has already been reported to break tests and isn’t what you intend.

Use destructuring to drop the real property name while avoiding ESLint’s unused-variable warning:

-      if (location) {
-        setUserLocation(location)
-        const sorted = sortChaptersByDistance(props.geoLocData, location)
-        setSortedData(sorted.map(({ _distance, ...chapter }) => chapter as unknown as Chapter))
-      }
+      if (location) {
+        setUserLocation(location)
+        const sorted = sortChaptersByDistance(props.geoLocData, location)
+        setSortedData(
+          sorted.map(({ distance: _, ...chapter }) => chapter as unknown as Chapter)
+        )
+      }

This removes distance from the mapped objects, satisfying the tests and keeping the runtime shape aligned with Chapter.

🧹 Nitpick comments (1)
frontend/src/components/ChapterMap.tsx (1)

16-27: Prop additions are fine; consider simplifying userLocation nullability

Adding optional userLocation and onShareLocation keeps existing call sites working and cleanly exposes the new behavior. Note that userLocation?: UserLocation | null encodes three states (undefined/null/value); if you don’t need to distinguish undefined vs null, you could later simplify this to a single “missing” state to reduce mental overhead.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1ea9a97 and eba33fa.

⛔ Files ignored due to path filters (1)
  • frontend/public/img/user-marker-icon.png is excluded by !**/*.png
📒 Files selected for processing (2)
  • frontend/src/components/ChapterMap.tsx (6 hunks)
  • frontend/src/components/ChapterMapWrapper.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-13T11:29:25.245Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: frontend/src/app/mentorship/programs/page.tsx:59-61
Timestamp: 2025-07-13T11:29:25.245Z
Learning: In Next.js 13+ app router, components with the 'use client' directive run entirely on the client side and don't require window object existence checks or SSR hydration considerations. Direct access to window.location and other browser APIs is safe in client components.

Applied to files:

  • frontend/src/components/ChapterMap.tsx
  • frontend/src/components/ChapterMapWrapper.tsx
🧬 Code graph analysis (1)
frontend/src/components/ChapterMap.tsx (3)
frontend/src/types/chapter.ts (1)
  • Chapter (4-21)
frontend/src/utils/geolocationUtils.ts (1)
  • UserLocation (1-6)
frontend/src/types/button.ts (1)
  • Button (4-9)
🔇 Additional comments (7)
frontend/src/components/ChapterMap.tsx (3)

2-5: New imports for location UI are consistent and scoped correctly

The additional FontAwesome, HeroUI, and UserLocation imports line up with how they’re used later in the file; no issues with tree‑shaking or type usage here.

Also applies to: 9-9


183-203: Overlay pointer-events change correctly accommodates the new control

Switching the overlay button to pointer-events-none with a pointer-events-auto inner <p> is a good way to:

  • Keep the “Click to interact with map” CTA clickable.
  • Avoid blocking interactions with the new location‑sharing button and the underlying map.

The behavior and accessibility of the activation overlay remain intact with the new control.


205-227: Location-sharing control wiring and accessibility look solid

The new bottom-left control is cleanly gated on onShareLocation, uses clear tooltip/aria text for both “share” and “reset” states, and delegates behavior correctly via the callback instead of owning state here. No issues from a UX or data‑flow perspective.

frontend/src/components/ChapterMapWrapper.tsx (4)

2-8: Geolocation utility imports are correct and type-safe

Using a single import for getUserLocationFromBrowser, sortChaptersByDistance, and type UserLocation keeps the API surface tidy and leverages type-only import for TS hygiene. Usage below matches these imports.


12-17: New props interface and showLocationSharing flag are well-shaped

Defining ChapterMapWrapperProps with an optional showLocationSharing?: boolean and later checking === true gives a clear opt-in without impacting existing call sites. This matches the requirement to keep default behavior unchanged unless explicitly enabled.


19-27: Wrapper state and enable/disable path preserve existing behavior

userLocation/sortedData are confined to this wrapper, and the early return when enableLocationSharing is false ensures the old ChapterMap behavior is preserved without needing changes elsewhere. This is a safe way to gate the new flow.


50-61: mapData selection and ChapterMap wiring look correct

mapData = sortedData ?? props.geoLocData cleanly prefers distance-sorted chapters when available and falls back to the original data. Passing userLocation and onShareLocation={handleShareLocation} through to ChapterMap keeps the concerns separated between data preparation and rendering.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
frontend/src/utils/geolocationUtils.ts (1)

1-6: Address the unresolved past review comment about unused fields.

The past review comment on these lines is still unresolved. The city and country fields are never populated by getUserLocationFromBrowser (which only returns latitude and longitude). Per the previous feedback, either remove these unused fields or add a clear comment explaining they are reserved for future enhancement (e.g., reverse geocoding).

Apply this diff to add documentation if keeping them for future use:

 export interface UserLocation {
   latitude: number
   longitude: number
+  // Reserved for future reverse-geocoding feature
   city?: string
   country?: string
 }

Or remove them if not planned:

 export interface UserLocation {
   latitude: number
   longitude: number
-  city?: string
-  country?: string
 }
🧹 Nitpick comments (1)
frontend/src/utils/geolocationUtils.ts (1)

57-61: Consider allowing brief location caching for better UX.

Setting maximumAge: 0 forces a fresh location request every time, meaning users will be prompted again if they refresh the page. For a chapter-proximity feature, consider allowing brief caching (e.g., maximumAge: 300000 for 5 minutes) to improve the user experience without sacrificing accuracy.

Apply this diff to enable brief caching:

       {
         enableHighAccuracy: false,
         timeout: 10000,
-        maximumAge: 0,
+        maximumAge: 300000, // Cache for 5 minutes
       }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eba33fa and f75ab07.

📒 Files selected for processing (1)
  • frontend/src/utils/geolocationUtils.ts (1 hunks)
🔇 Additional comments (3)
frontend/src/utils/geolocationUtils.ts (3)

8-11: LGTM!

The internal ChapterCoordinates interface is clean and appropriately typed for the helper function.


13-29: LGTM!

The Haversine formula implementation is mathematically correct and will accurately calculate great-circle distances between coordinates.


66-99: LGTM!

Both extractChapterCoordinates and sortChaptersByDistance are correctly implemented. The coordinate extraction properly handles the fallback chain, and the sorting logic correctly filters out invalid coordinates and orders chapters by proximity.

@anurag2787
Copy link
Contributor Author

Hi @kasya i have updated the changes as per your review could you please take a look and let me know if everything looks good so that i can proceed with the writing test part

@anurag2787 anurag2787 requested review from arkid15r and kasya December 9, 2025 20:18
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
frontend/src/components/ChapterMap.tsx (2)

159-159: Fix pre-existing off-by-one error for consistency.

Line 159 uses slice(0, maxNearestChapters - 1), which returns only 4 chapters instead of the intended 5. The new user location path at line 140 correctly uses slice(0, maxNearestChapters). Fix this pre-existing bug for consistency:

-const localChapters = validGeoLocData.slice(0, maxNearestChapters - 1)
+const localChapters = validGeoLocData.slice(0, maxNearestChapters)

115-136: User location marker is never removed, causing stale/duplicate markers.

Each effect run adds a new user marker without removing the previous one. When userLocation changes or is cleared via "Reset location filter", old markers remain on the map, causing accumulation.

Track the user marker in a ref and remove it at the start of each effect run:

 const mapRef = useRef<L.Map | null>(null)
 const markerClusterRef = useRef<MarkerClusterGroup | null>(null)
+const userMarkerRef = useRef<L.Marker | null>(null)
 const [isMapActive, setIsMapActive] = useState(false)

Then in the effect, before adding a new marker:

 const map = mapRef.current

+// Remove any existing user marker before re-adding or when location is cleared
+if (userMarkerRef.current && map) {
+  map.removeLayer(userMarkerRef.current)
+  userMarkerRef.current = null
+}
+
 // Add user location marker if available
 if (userLocation && map) {
   ...
   userMarker.addTo(map)
+  userMarkerRef.current = userMarker
 }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f75ab07 and 3ce07f1.

📒 Files selected for processing (1)
  • frontend/src/components/ChapterMap.tsx (6 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-13T11:29:25.245Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: frontend/src/app/mentorship/programs/page.tsx:59-61
Timestamp: 2025-07-13T11:29:25.245Z
Learning: In Next.js 13+ app router, components with the 'use client' directive run entirely on the client side and don't require window object existence checks or SSR hydration considerations. Direct access to window.location and other browser APIs is safe in client components.

Applied to files:

  • frontend/src/components/ChapterMap.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Run spell check
  • GitHub Check: Run frontend checks
  • GitHub Check: Run pre-commit checks
🔇 Additional comments (4)
frontend/src/components/ChapterMap.tsx (4)

2-9: LGTM! Imports are appropriate for the new functionality.

All new imports are necessary for the location-sharing feature and are from the correct packages.


20-27: LGTM! Props are correctly typed and appropriately optional.

The optional nature of these props maintains backward compatibility while enabling the new location-sharing feature.


138-156: LGTM! Map centering logic for user location is correct.

The logic properly centers on the nearest chapter and fits bounds using the first 5 chapters with appropriate zoom level.


177-177: LGTM! Dependencies correctly updated.

Adding userLocation to the dependency array ensures the effect re-runs when the location changes, which is necessary for updating the marker and map view.

Copy link
Collaborator

@kasya kasya left a comment

Choose a reason for hiding this comment

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

@anurag2787 Thanks for updating the PR!

I pushed a few more changes. The styling of the button was not really matching with the existing zoom buttons styling (size, missing borders, border radius, etc).
Also the custom Pin image you added had watermarks all over it and did not match the pins we have on the map.
The tooltip was also misaligned with the button itself.

Please pay more attention to details next time 💯

I updated the code to use existing image of a pin that Leaflet uses and adjusted the color of it programmatically.
We also decided to move the button up to be aligned with zoom buttons.


I will approve this PR as it is right now, but I also created a separate issue for you to enhance current behavior of this button. All the requirements for that are listed in the issue 👌🏼 . The required updates are pretty small, but needed.

@kasya kasya added this pull request to the merge queue Dec 13, 2025
Merged via the queue into OWASP:main with commit 670fdff Dec 14, 2025
25 checks passed
@sonarqubecloud
Copy link

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.

Add precise location sharing option for chapter map

3 participants