feat: Add user profile page with detailed card, display saved routes, and introduce a route discovery panel.#10
Conversation
- Added HomeMap component to display a Mapbox map with geolocation features. - Updated Home page to render the HomeMap component. - Introduced PrivateLayout component for consistent layout structure. - Updated TypeScript configuration to include additional type definitions. - Added Mapbox GL and related dependencies to package.json and package-lock.json. - Created global.d.ts for Mapbox CSS module declaration.
…n points and define route data schema.
…nation selection with search and geolocation.
…th health insights.
…ith proper routing
…n insights and map integration.
… Mapbox integration.
… and introduce a route discovery panel.
📝 WalkthroughWalkthroughMultiple files updated to refactor route saving mechanism with dynamic multi-route payload serialization and unit conversions (meters→km, seconds→minutes), add server-side data fetching for saved routes display, introduce client-side presentational component for route links, update UI components with new props and layout enhancements, and fix minor cleanup logic in map markers handling. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
client/components/profile/ProfileCard.tsx (1)
59-62:⚠️ Potential issue | 🟡 MinorHardcoded location string.
"New Delhi, India" is static for all users. If this is a placeholder, consider either sourcing it from the
UserDataobject or removing it until user location data is available, to avoid misleading information.
🤖 Fix all issues with AI agents
In `@client/app/`(private)/home/routes/(from)/(to)/page.tsx:
- Around line 248-255: The mapping that builds route records uses a
nondeterministic fallback for lastComputedScore (route.aqiScore ||
Math.floor(Math.random() * 100)) which causes random values to be persisted;
change the logic in the routes.map block to use a clear sentinel (e.g., null or
0) when route.aqiScore is falsy instead of generating random data, update any
DB/save code that consumes lastComputedScore to accept the chosen sentinel, and
ensure the field name lastComputedScore and the source route.aqiScore are the
ones updated so stored values are deterministic and meaningful.
In `@client/components/profile/ProfileCard.tsx`:
- Around line 29-30: The Next.js Image in ProfileCard.tsx is being rendered with
CSS classes h-28 w-28 (112×112px) but is created with width={100} height={100},
causing upscaling and blur; update the Image props (the width and height
attributes on the Image component in ProfileCard) to match the rendered CSS size
(use width={112} height={112}) or alternatively switch to a responsive/sizes
approach (e.g., remove fixed width/height and use layout/sizes) so the generated
source matches the displayed h-28 w-28 dimensions.
In `@client/components/profile/SavedRoutes.tsx`:
- Around line 81-82: The AQI fallback currently uses "const aqi =
route.routes?.[0]?.lastComputedScore ?? 0" which causes missing scores to be
treated as 0 and renders "Good"; instead detect missing data and pass
null/undefined through so the UI shows "N/A". Update SavedRoutes.tsx to read the
score from route.routes?.[0]?.lastComputedScore without defaulting to 0 (e.g.,
let aqi = route.routes?.[0]?.lastComputedScore), ensure getAqiBadge can accept
null/undefined (or add a local conditional) and change the display logic that
renders badge/text to handle a null score by showing "N/A" rather than calling
getAqiBadge(0).
🧹 Nitpick comments (7)
client/components/saved-routes/SavedRoutesView.tsx (1)
102-108:overflow-automay not trigger without a height constraint.The
overflow-autoclass only produces a scrollbar when the element has a bounded height. In this flex layout, the<div>doesn't have an explicit height (e.g.,h-screen,max-h-screen, orh-fullwith a sized parent), so it may simply grow with its content instead of scrolling.Consider adding a height constraint, for example:
Proposed fix
- <div className="hidden overflow-auto xl:block"> + <div className="hidden max-h-screen overflow-auto xl:block">client/app/(private)/home/routes/(from)/(to)/page.tsx (3)
7-7: Unused import:Bookmarkis no longer used in this file.The save button UI was moved to
RouteDiscoveryPanel, so this import is dead code.-import { Bookmark } from "lucide-react";
272-272: Removeconsole.logdebug artifact before merging.This logs the full save-route response payload to the browser console in production.
- console.log("Save route response:", data);
69-91:reverseGeocodeis missing from theuseEffectdependency array.
reverseGeocodeis defined as a component method but not memoized, so the exhaustive-deps lint rule would flag this. Since it only closes over the module-levelMAPBOX_TOKEN, it's safe in practice, but consider either moving it outside the component or wrapping it withuseCallbackto satisfy the linter.Also applies to: 94-123
client/components/profile/SavedRouteItemClient.tsx (1)
9-17:routeIdprop is accepted but unused — consider deferring the prop until it's needed.The static analysis linter flags
_routeIdas unused. Since the component currently has no client-side logic (no hooks, no event handlers), it's effectively a pure presentational server-renderable component. IfrouteIdis planned for future use, a brief TODO would clarify intent; otherwise, remove the prop to keep the interface honest.Also, the exported function name
SavedRouteLinkdoesn't match the filenameSavedRouteItemClient.tsx— consider aligning them for discoverability.client/components/profile/SavedRoutes.tsx (2)
24-31: Inconsistent caching strategy withgetUser()inpage.tsx.
getUser()usescache: "no-store"whilegetTopRoutes()usesnext: { revalidate: 0 }. While both prevent stale data, they have subtly different semantics in Next.js —cache: "no-store"fully opts out of the fetch cache, whereasrevalidate: 0still participates in it but revalidates immediately. For consistency and clarity, consider using the same approach (cache: "no-store") in both places.
84-88: Hardcoded"en-IN"locale for date formatting.This forces Indian English date formatting for all users regardless of their actual locale. If i18n is a concern, consider deriving the locale from user preferences or the
Accept-Languageheader.
| routes: routes.map((route) => ({ | ||
| distance: route.distance / 1000, | ||
| duration: route.duration / 60, | ||
| routeGeometry: route.geometry, | ||
| lastComputedScore: route.aqiScore || Math.floor(Math.random() * 100), | ||
| lastComputedAt: new Date(), | ||
| travelMode: selectedMode, | ||
| })), |
There was a problem hiding this comment.
Non-deterministic fallback for lastComputedScore persists random data to the backend.
When route.aqiScore is falsy, Math.floor(Math.random() * 100) generates a random score that gets saved to the database. This means:
- Retrying the same save produces different stored scores.
- The saved score has no relation to actual air quality.
Consider using a sentinel value (e.g., 0 or null) to clearly indicate "no score available" rather than fabricating data.
- lastComputedScore: route.aqiScore || Math.floor(Math.random() * 100),
+ lastComputedScore: route.aqiScore ?? null,📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| routes: routes.map((route) => ({ | |
| distance: route.distance / 1000, | |
| duration: route.duration / 60, | |
| routeGeometry: route.geometry, | |
| lastComputedScore: route.aqiScore || Math.floor(Math.random() * 100), | |
| lastComputedAt: new Date(), | |
| travelMode: selectedMode, | |
| })), | |
| routes: routes.map((route) => ({ | |
| distance: route.distance / 1000, | |
| duration: route.duration / 60, | |
| routeGeometry: route.geometry, | |
| lastComputedScore: route.aqiScore ?? null, | |
| lastComputedAt: new Date(), | |
| travelMode: selectedMode, | |
| })), |
🤖 Prompt for AI Agents
In `@client/app/`(private)/home/routes/(from)/(to)/page.tsx around lines 248 -
255, The mapping that builds route records uses a nondeterministic fallback for
lastComputedScore (route.aqiScore || Math.floor(Math.random() * 100)) which
causes random values to be persisted; change the logic in the routes.map block
to use a clear sentinel (e.g., null or 0) when route.aqiScore is falsy instead
of generating random data, update any DB/save code that consumes
lastComputedScore to accept the chosen sentinel, and ensure the field name
lastComputedScore and the source route.aqiScore are the ones updated so stored
values are deterministic and meaningful.
| width={100} | ||
| height={100} |
There was a problem hiding this comment.
Image intrinsic size doesn't match rendered CSS size.
h-28 w-28 resolves to 112×112 px, but width={100} height={100} tells Next.js to generate a 100 px source image. This causes upscaling and slight blur.
Proposed fix
- width={100}
- height={100}
+ width={112}
+ height={112}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| width={100} | |
| height={100} | |
| width={112} | |
| height={112} |
🤖 Prompt for AI Agents
In `@client/components/profile/ProfileCard.tsx` around lines 29 - 30, The Next.js
Image in ProfileCard.tsx is being rendered with CSS classes h-28 w-28
(112×112px) but is created with width={100} height={100}, causing upscaling and
blur; update the Image props (the width and height attributes on the Image
component in ProfileCard) to match the rendered CSS size (use width={112}
height={112}) or alternatively switch to a responsive/sizes approach (e.g.,
remove fixed width/height and use layout/sizes) so the generated source matches
the displayed h-28 w-28 dimensions.
| const aqi = route.routes?.[0]?.lastComputedScore ?? 0; | ||
| const badge = getAqiBadge(aqi); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
fd "types.ts" -o "interface.ts" -o "*types*" | head -20Repository: kaihere14/BreathClean
Length of output: 208
🏁 Script executed:
rg "IRouteOption|lastComputedScore" -t ts -t tsx --max-count=50Repository: kaihere14/BreathClean
Length of output: 92
🏁 Script executed:
rg "getAqiBadge" -t ts -t tsx -A 10Repository: kaihere14/BreathClean
Length of output: 92
🏁 Script executed:
cat -n client/components/profile/SavedRoutes.tsx | sed -n '75,90p'Repository: kaihere14/BreathClean
Length of output: 717
🏁 Script executed:
find . -type f -name "*.ts" -o -name "*.tsx" | grep -E "(type|interface)" | head -20Repository: kaihere14/BreathClean
Length of output: 238
🏁 Script executed:
rg "IRouteOption" --max-count=20Repository: kaihere14/BreathClean
Length of output: 521
🏁 Script executed:
rg "getAqiBadge" -A 15Repository: kaihere14/BreathClean
Length of output: 7056
🏁 Script executed:
rg "lastComputedScore" -B 3 -A 3Repository: kaihere14/BreathClean
Length of output: 9106
AQI fallback of 0 is misleading — missing data displays as "Good" air quality.
When lastComputedScore is missing or routes is empty, the fallback ?? 0 results in getAqiBadge(0) returning "Good". This contradicts the intent: users see excellent air quality when no data is available. The codebase already handles this correctly elsewhere (e.g., RouteInsightsPanel.tsx), where null/undefined scores display as "N/A". Align this component by explicitly handling the no-data case.
Suggested fix
Change the getAqiBadge function to accept and handle null values:
-function getAqiBadge(aqi: number) {
+function getAqiBadge(aqi: number | null | undefined) {
+ if (aqi == null)
+ return { label: "N/A", color: "bg-slate-100 text-slate-500" };
if (aqi <= 50)
return { label: "Good", color: "bg-emerald-100 text-emerald-700" };Then update line 81:
- const aqi = route.routes?.[0]?.lastComputedScore ?? 0;
+ const aqi = route.routes?.[0]?.lastComputedScore;And update the display to handle null:
- AQI {aqi} · {badge.label}
+ {aqi !== null && aqi !== undefined ? `AQI ${aqi} · ` : ""}{badge.label}🤖 Prompt for AI Agents
In `@client/components/profile/SavedRoutes.tsx` around lines 81 - 82, The AQI
fallback currently uses "const aqi = route.routes?.[0]?.lastComputedScore ?? 0"
which causes missing scores to be treated as 0 and renders "Good"; instead
detect missing data and pass null/undefined through so the UI shows "N/A".
Update SavedRoutes.tsx to read the score from
route.routes?.[0]?.lastComputedScore without defaulting to 0 (e.g., let aqi =
route.routes?.[0]?.lastComputedScore), ensure getAqiBadge can accept
null/undefined (or add a local conditional) and change the display logic that
renders badge/text to handle a null score by showing "N/A" rather than calling
getAqiBadge(0).
Summary by CodeRabbit
New Features
Bug Fixes
Performance