Skip to content

feat: Add user profile page with detailed card, display saved routes, and introduce a route discovery panel.#10

Merged
kaihere14 merged 20 commits into
kaihere14:mainfrom
GURUDAS-DEV:main
Feb 14, 2026
Merged

feat: Add user profile page with detailed card, display saved routes, and introduce a route discovery panel.#10
kaihere14 merged 20 commits into
kaihere14:mainfrom
GURUDAS-DEV:main

Conversation

@GURUDAS-DEV

@GURUDAS-DEV GURUDAS-DEV commented Feb 14, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Display multiple routes dynamically with distance and duration in user-friendly units.
    • Integrated save route functionality into the route discovery interface.
    • Fetch and display user's saved routes with AQI badges and last-used dates.
    • Added back navigation button in route details panel.
  • Bug Fixes

    • Improved marker cleanup and image sizing for better layout stability.
  • Performance

    • Added loading skeleton for saved routes page.

GURUDAS-DEV and others added 20 commits February 14, 2026 12:20
- 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.
…nation selection with search and geolocation.
@coderabbitai

coderabbitai Bot commented Feb 14, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Multiple 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

Cohort / File(s) Summary
Route Saving & Discovery
client/app/(private)/home/routes/(from)/(to)/page.tsx, client/components/routes/RouteDiscoveryPanel.tsx
Updated save action to serialize all routes with converted units (distance km, duration minutes) and renamed fields (routeGeometry, lastComputedScore). RouteDiscoveryPanel now accepts onSaveRoute callback and canSave boolean prop, replacing inline save button with conditional control.
Saved Routes Profile Display
client/components/profile/SavedRoutes.tsx, client/components/profile/SavedRouteItemClient.tsx
Converted SavedRoutes to async server component with server-side API fetching using refresh token and cookies. Added new client-side SavedRouteLink presentational component. Dynamic route list with AQI badges, last-used dates, and navigation links replaces static in-memory data.
Profile & Layout Updates
client/app/(private)/profile/page.tsx, client/components/profile/ProfileCard.tsx, client/app/layout.tsx
Wrapped SavedRoutes in Suspense boundary with skeleton fallback. Added explicit width/height props to ProfileCard Image component. Removed unused toast export from Sonner import.
Component Refinements
client/components/home/HomeMap.tsx, client/components/saved-routes/SavedRoutesView.tsx
Captured markersRef.current value before cleanup to prevent stale reference access. Added overflow-auto class to RouteInsightsPanel wrapper for vertical scrolling support.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 Routes now dance in dynamic arrays,
Saved with kilometers and measured time,
Server whispers data through the stars,
While sketches load with style sublime,
Markers cleanup without a care! ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: adding a user profile page with detailed card, displaying saved routes, and introducing route discovery panel integration.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 | 🟡 Minor

Hardcoded location string.

"New Delhi, India" is static for all users. If this is a placeholder, consider either sourcing it from the UserData object 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-auto may not trigger without a height constraint.

The overflow-auto class 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, or h-full with 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: Bookmark is 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: Remove console.log debug 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: reverseGeocode is missing from the useEffect dependency array.

reverseGeocode is defined as a component method but not memoized, so the exhaustive-deps lint rule would flag this. Since it only closes over the module-level MAPBOX_TOKEN, it's safe in practice, but consider either moving it outside the component or wrapping it with useCallback to satisfy the linter.

Also applies to: 94-123

client/components/profile/SavedRouteItemClient.tsx (1)

9-17: routeId prop is accepted but unused — consider deferring the prop until it's needed.

The static analysis linter flags _routeId as unused. Since the component currently has no client-side logic (no hooks, no event handlers), it's effectively a pure presentational server-renderable component. If routeId is planned for future use, a brief TODO would clarify intent; otherwise, remove the prop to keep the interface honest.

Also, the exported function name SavedRouteLink doesn't match the filename SavedRouteItemClient.tsx — consider aligning them for discoverability.

client/components/profile/SavedRoutes.tsx (2)

24-31: Inconsistent caching strategy with getUser() in page.tsx.

getUser() uses cache: "no-store" while getTopRoutes() uses next: { 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, whereas revalidate: 0 still 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-Language header.

Comment on lines +248 to +255
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,
})),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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:

  1. Retrying the same save produces different stored scores.
  2. 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.

Suggested change
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.

Comment on lines +29 to +30
width={100}
height={100}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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.

Comment on lines +81 to +82
const aqi = route.routes?.[0]?.lastComputedScore ?? 0;
const badge = getAqiBadge(aqi);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

fd "types.ts" -o "interface.ts" -o "*types*" | head -20

Repository: kaihere14/BreathClean

Length of output: 208


🏁 Script executed:

rg "IRouteOption|lastComputedScore" -t ts -t tsx --max-count=50

Repository: kaihere14/BreathClean

Length of output: 92


🏁 Script executed:

rg "getAqiBadge" -t ts -t tsx -A 10

Repository: 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 -20

Repository: kaihere14/BreathClean

Length of output: 238


🏁 Script executed:

rg "IRouteOption" --max-count=20

Repository: kaihere14/BreathClean

Length of output: 521


🏁 Script executed:

rg "getAqiBadge" -A 15

Repository: kaihere14/BreathClean

Length of output: 7056


🏁 Script executed:

rg "lastComputedScore" -B 3 -A 3

Repository: 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).

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants