scroller site#105
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
More reviews will be available in 12 minutes and 25 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThis PR adds a homepage daily forecast ticker, filters hourly forecast display to the current hour onward, exports ChangesDaily Forecast Ticker and Platform Styling
Hourly Forecast & Responsive Layouts
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsErrors were encountered while retrieving linked issues. Errors (1)
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
🧹 Nitpick comments (6)
frontend/src/components/map/LeafletMap.tsx (2)
1344-1345: 💤 Low valueConsider clarifying the empty state message.
The empty state check correctly uses
visibleChartData.length, which is the right behavior for time-window-aware rendering.The message "No hourly forecast data." could optionally be made more specific to indicate that data might exist but is outside the current time window (e.g., "No hourly forecast data available from this time onwards."). However, the current message is acceptable.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/components/map/LeafletMap.tsx` around lines 1344 - 1345, Update the empty-state text in the LeafletMap component where it checks visibleChartData.length to make it clearer the absence may be due to the selected time window; replace "No hourly forecast data." with a more specific message such as "No hourly forecast data available for the selected time window." so users know data might exist but is outside the current time range.
1115-1122: ⚡ Quick winConsider adding a brief comment to clarify the filtering intent.
The time-window filtering logic is correct and well-implemented. However, a short comment explaining that this filters to show only forecast hours from the current hour onwards would improve code maintainability for future developers.
📝 Suggested comment
+ // Filter forecast data to show only hours from the current hour onwards const visibleChartData = useMemo(() => { if (!currentTime) return chartData const forecastWindowStart = new Date(currentTime) forecastWindowStart.setMinutes(0, 0, 0) return chartData.filter((row) => row.time >= forecastWindowStart.getTime()) }, [chartData, currentTime])🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/components/map/LeafletMap.tsx` around lines 1115 - 1122, Add a brief inline comment above the useMemo that computes visibleChartData explaining the intent: it filters chartData to only include forecast rows from the current hour onward by creating forecastWindowStart (a Date derived from currentTime with minutes/seconds/milliseconds zeroed) and comparing row.time to forecastWindowStart.getTime(); reference visibleChartData, forecastWindowStart, chartData, currentTime and useMemo so future readers understand the time-window filtering behavior.frontend/src/components/charts/AirQualityChart.tsx (1)
457-692: ⚡ Quick winConsider applying the same responsive patterns to WeeklyComparisonChart and AQIIndexVisual.
The responsive header padding (
p-4 sm:p-6), grid-based control layouts, and responsive content padding (p-3 sm:p-6) were applied toPM25BarChartandAQICategoryChart, butWeeklyComparisonChart(lines 457-692) andAQIIndexVisual(lines 695-803) still use the older layout patterns.While this may be intentional based on the layer scope, applying consistent responsive patterns across all chart components would improve maintainability and user experience uniformity.
♻️ Example responsive updates for WeeklyComparisonChart
For the CardHeader (around line 553):
- <CardHeader> + <CardHeader className="p-4 sm:p-6">For the CardContent (around line 632):
- <CardContent> + <CardContent className="p-3 sm:p-6">And optionally update the control rows to use the grid pattern for consistency.
Also applies to: 695-803
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/components/charts/AirQualityChart.tsx` around lines 457 - 692, WeeklyComparisonChart and AQIIndexVisual use older layout patterns; update their CardHeader and CardContent to match the responsive patterns used elsewhere (e.g., add header padding classes like p-4 sm:p-6 and content padding like p-3 sm:p-6), refactor the controls container (the div that holds the Select components) to a responsive grid (e.g., grid with grid-cols-1 sm:grid-cols-2 md:grid-cols-4 and gap utilities) so controls wrap consistently, and ensure the warning paragraph and chart container retain appropriate spacing (adjust margins/padding to match the new CardContent spacing); locate these changes around the WeeklyComparisonChart and AQIIndexVisual components to apply the same class updates.frontend/src/services/apiService.tsx (1)
44-60: ⚡ Quick winUse the exported
MapNodeas the single contract.Now that
MapNodeis exported here,frontend/src/components/map/LeafletMap.tsxshould import this type instead of keeping a second localinterface MapNode. Leaving both in place makes the API shape easy to drift out of sync across the homepage ticker and map views.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/services/apiService.tsx` around lines 44 - 60, Replace the duplicate local interface in LeafletMap.tsx with the exported MapNode from frontend/src/services/apiService.tsx: remove the local interface MapNode declaration in LeafletMap.tsx and add an import for MapNode (the exported symbol) then update any references in the file to use that imported MapNode type so both map and other components share the single contract.frontend/src/app/page.tsx (1)
1-3: ⚡ Quick winKeep the page server-rendered and isolate the ticker behind its own client boundary.
Only
DailyForecastTickerneeds browser APIs, but the page-level"use client"moves the entire landing page tree into the client bundle. Extract the ticker into a separate client component and keeppage.tsxas a server component so the rest of the homepage stays lighter.Also applies to: 433-643
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/app/page.tsx` around lines 1 - 3, page.tsx is marked as a client component but only DailyForecastTicker needs browser APIs; remove the top-level "use client" from page.tsx so the page remains a server component, and move the ticker logic into its own client component file (e.g., DailyForecastTicker.tsx) that begins with "use client" and contains all useEffect/useState hooks and browser-only code from the previous DailyForecastTicker; then update the import in page.tsx to import the new DailyForecastTicker client component (default or named) so the server-rendered page composes the ticker as a client boundary while the rest of the homepage stays server-rendered.frontend/src/app/locate/page.tsx (1)
297-297: 💤 Low valueConsider map legend visibility on mobile.
The legend is completely hidden on small screens (
hidden sm:block), which means mobile users won't have a reference for understanding marker symbols. While marker popups provide this information on interaction, having the legend visible could improve immediate comprehension.Consider either:
- Keeping a more compact version of the legend visible on mobile
- Adding a collapsible legend button on mobile
- Accepting the current UX if analytics show mobile users primarily interact with markers anyway
The current implementation is functional since popups fill the gap, but it's worth evaluating mobile user behavior.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/app/locate/page.tsx` at line 297, The legend div currently uses the Tailwind classes "hidden sm:block" which fully hides it on mobile; update the UI so mobile users can access a compact or collapsible legend: either remove the "hidden" and replace with a mobile-compact variant (e.g., smaller padding, icons-only) or implement a toggle button and state (e.g., showLegend boolean) that renders a compact Legend component on screens <sm and the full legend on >=sm; locate the div with className "absolute bottom-5 left-5 z-[1000] hidden rounded-2xl border border-white/70 bg-white/95 p-3 shadow-lg backdrop-blur sm:block" in page.tsx and refactor it into a responsive Legend component plus an optional mobile button to open/close it.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@frontend/src/app/page.tsx`:
- Around line 131-135: The formatForecastDate function treats "YYYY-MM-DD"
strings as UTC, causing a day shift for some users; update formatForecastDate to
detect date-only inputs (e.g., /^\d{4}-\d{2}-\d{2}$/) and construct a local Date
via new Date(year, monthIndex, day) instead of new Date(value), then fall back
to the existing parsing for other inputs and keep returning the
toLocaleDateString with { weekday: "short", month: "short", day: "numeric" } as
before.
- Around line 154-185: The resolveCountry function currently falls back to the
nearest MapNode anywhere in the world; guard that fallback with a configurable
coverage radius (e.g., const FALLBACK_RADIUS_KM) and only return the nearest
node's siteDetails.country if its distance (computed with getDistanceInKm) is <=
FALLBACK_RADIUS_KM; otherwise return undefined/null. Update the reduce logic in
resolveCountry (or compute nearest with a loop) to capture the nearest distance
and compare it to FALLBACK_RADIUS_KM before returning
node.siteDetails?.country?.trim(), and add the new constant and unit tests or
comments indicating the chosen radius.
- Around line 265-269: The error callback currently unconditionally sets state
to "denied" and persists that to FORECAST_TICKER_LOCATION_STATE_KEY; instead
inspect the GeolocationPositionError.code in the callback (compare against
error.PERMISSION_DENIED, error.POSITION_UNAVAILABLE, error.TIMEOUT) and map to
distinct states (e.g., "denied" for PERMISSION_DENIED, "unavailable" for
POSITION_UNAVAILABLE, "timeout" for TIMEOUT, or "error" fallback), call
setLocationState with that mapped value, store that mapped value to
FORECAST_TICKER_LOCATION_STATE_KEY, and only remove FORECAST_TICKER_COUNTRY_KEY
when the mapped state is "denied" (permission permanently denied) so temporary
failures don’t persist as "denied".
---
Nitpick comments:
In `@frontend/src/app/locate/page.tsx`:
- Line 297: The legend div currently uses the Tailwind classes "hidden sm:block"
which fully hides it on mobile; update the UI so mobile users can access a
compact or collapsible legend: either remove the "hidden" and replace with a
mobile-compact variant (e.g., smaller padding, icons-only) or implement a toggle
button and state (e.g., showLegend boolean) that renders a compact Legend
component on screens <sm and the full legend on >=sm; locate the div with
className "absolute bottom-5 left-5 z-[1000] hidden rounded-2xl border
border-white/70 bg-white/95 p-3 shadow-lg backdrop-blur sm:block" in page.tsx
and refactor it into a responsive Legend component plus an optional mobile
button to open/close it.
In `@frontend/src/app/page.tsx`:
- Around line 1-3: page.tsx is marked as a client component but only
DailyForecastTicker needs browser APIs; remove the top-level "use client" from
page.tsx so the page remains a server component, and move the ticker logic into
its own client component file (e.g., DailyForecastTicker.tsx) that begins with
"use client" and contains all useEffect/useState hooks and browser-only code
from the previous DailyForecastTicker; then update the import in page.tsx to
import the new DailyForecastTicker client component (default or named) so the
server-rendered page composes the ticker as a client boundary while the rest of
the homepage stays server-rendered.
In `@frontend/src/components/charts/AirQualityChart.tsx`:
- Around line 457-692: WeeklyComparisonChart and AQIIndexVisual use older layout
patterns; update their CardHeader and CardContent to match the responsive
patterns used elsewhere (e.g., add header padding classes like p-4 sm:p-6 and
content padding like p-3 sm:p-6), refactor the controls container (the div that
holds the Select components) to a responsive grid (e.g., grid with grid-cols-1
sm:grid-cols-2 md:grid-cols-4 and gap utilities) so controls wrap consistently,
and ensure the warning paragraph and chart container retain appropriate spacing
(adjust margins/padding to match the new CardContent spacing); locate these
changes around the WeeklyComparisonChart and AQIIndexVisual components to apply
the same class updates.
In `@frontend/src/components/map/LeafletMap.tsx`:
- Around line 1344-1345: Update the empty-state text in the LeafletMap component
where it checks visibleChartData.length to make it clearer the absence may be
due to the selected time window; replace "No hourly forecast data." with a more
specific message such as "No hourly forecast data available for the selected
time window." so users know data might exist but is outside the current time
range.
- Around line 1115-1122: Add a brief inline comment above the useMemo that
computes visibleChartData explaining the intent: it filters chartData to only
include forecast rows from the current hour onward by creating
forecastWindowStart (a Date derived from currentTime with
minutes/seconds/milliseconds zeroed) and comparing row.time to
forecastWindowStart.getTime(); reference visibleChartData, forecastWindowStart,
chartData, currentTime and useMemo so future readers understand the time-window
filtering behavior.
In `@frontend/src/services/apiService.tsx`:
- Around line 44-60: Replace the duplicate local interface in LeafletMap.tsx
with the exported MapNode from frontend/src/services/apiService.tsx: remove the
local interface MapNode declaration in LeafletMap.tsx and add an import for
MapNode (the exported symbol) then update any references in the file to use that
imported MapNode type so both map and other components share the single
contract.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: d0a07baa-e251-4bb2-96d8-082c70fa3092
📒 Files selected for processing (8)
frontend/src/app/categorize/page.tsxfrontend/src/app/locate/page.tsxfrontend/src/app/page.tsxfrontend/src/app/reports/page.tsxfrontend/src/components/charts/AirQualityChart.tsxfrontend/src/components/map/LeafletMap.tsxfrontend/src/services/apiService.tsxfrontend/src/styles/globals.css
| function formatForecastDate(value: string) { | ||
| const date = new Date(value) | ||
| if (Number.isNaN(date.getTime())) return "Daily outlook" | ||
| return date.toLocaleDateString(undefined, { weekday: "short", month: "short", day: "numeric" }) | ||
| } |
There was a problem hiding this comment.
Parse date-only forecast strings as local dates.
new Date("YYYY-MM-DD") is interpreted as UTC, so users west of UTC can see the previous day in the ticker. This will shift the forecast label by a day for some visitors.
Suggested fix
function formatForecastDate(value: string) {
- const date = new Date(value)
+ const [year, month, day] = value.split("-").map(Number)
+ const date =
+ Number.isInteger(year) && Number.isInteger(month) && Number.isInteger(day)
+ ? new Date(year, month - 1, day)
+ : new Date(value)
if (Number.isNaN(date.getTime())) return "Daily outlook"
return date.toLocaleDateString(undefined, { weekday: "short", month: "short", day: "numeric" })
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/app/page.tsx` around lines 131 - 135, The formatForecastDate
function treats "YYYY-MM-DD" strings as UTC, causing a day shift for some users;
update formatForecastDate to detect date-only inputs (e.g.,
/^\d{4}-\d{2}-\d{2}$/) and construct a local Date via new Date(year, monthIndex,
day) instead of new Date(value), then fall back to the existing parsing for
other inputs and keep returning the toLocaleDateString with { weekday: "short",
month: "short", day: "numeric" } as before.
| async function resolveCountry(latitude: number, longitude: number, mapNodes: MapNode[]) { | ||
| const mapboxToken = process.env.NEXT_PUBLIC_MAPBOX_TOKEN | ||
| if (mapboxToken) { | ||
| const url = new URL("https://api.mapbox.com/search/geocode/v6/reverse") | ||
| url.searchParams.set("longitude", String(longitude)) | ||
| url.searchParams.set("latitude", String(latitude)) | ||
| url.searchParams.set("types", "country") | ||
| url.searchParams.set("limit", "1") | ||
| url.searchParams.set("access_token", mapboxToken) | ||
|
|
||
| try { | ||
| const response = await fetch(url, { cache: "no-store" }) | ||
| if (response.ok) { | ||
| const data = (await response.json()) as MapboxCountryResponse | ||
| const country = | ||
| data.features?.[0]?.properties?.name || | ||
| data.features?.[0]?.properties?.context?.country?.name | ||
| if (country) return country | ||
| } | ||
| } catch (error) { | ||
| console.error("Unable to reverse geocode visitor country:", error) | ||
| } | ||
| } | ||
|
|
||
| return mapNodes | ||
| .filter((node) => Boolean(node.siteDetails?.country)) | ||
| .reduce<{ node: MapNode; distance: number } | null>((nearest, node) => { | ||
| const distance = getDistanceInKm(latitude, longitude, node) | ||
| return !nearest || distance < nearest.distance ? { node, distance } : nearest | ||
| }, null) | ||
| ?.node.siteDetails?.country?.trim() | ||
| } |
There was a problem hiding this comment.
Guard the nearest-node fallback with a coverage radius.
If reverse geocoding is unavailable, this code always assigns the country of the closest AirQo node anywhere in the world. For users outside the monitored footprint, that can silently select the wrong country and show irrelevant forecasts. Return no country unless the nearest node is within a sane fallback radius.
Suggested fix
async function resolveCountry(latitude: number, longitude: number, mapNodes: MapNode[]) {
+ const MAX_FALLBACK_DISTANCE_KM = 250
const mapboxToken = process.env.NEXT_PUBLIC_MAPBOX_TOKEN
if (mapboxToken) {
const url = new URL("https://api.mapbox.com/search/geocode/v6/reverse")
@@
- return mapNodes
+ const nearest = mapNodes
.filter((node) => Boolean(node.siteDetails?.country))
.reduce<{ node: MapNode; distance: number } | null>((nearest, node) => {
const distance = getDistanceInKm(latitude, longitude, node)
return !nearest || distance < nearest.distance ? { node, distance } : nearest
}, null)
- ?.node.siteDetails?.country?.trim()
+
+ return nearest && nearest.distance <= MAX_FALLBACK_DISTANCE_KM
+ ? nearest.node.siteDetails?.country?.trim()
+ : undefined
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/app/page.tsx` around lines 154 - 185, The resolveCountry
function currently falls back to the nearest MapNode anywhere in the world;
guard that fallback with a configurable coverage radius (e.g., const
FALLBACK_RADIUS_KM) and only return the nearest node's siteDetails.country if
its distance (computed with getDistanceInKm) is <= FALLBACK_RADIUS_KM; otherwise
return undefined/null. Update the reduce logic in resolveCountry (or compute
nearest with a loop) to capture the nearest distance and compare it to
FALLBACK_RADIUS_KM before returning node.siteDetails?.country?.trim(), and add
the new constant and unit tests or comments indicating the chosen radius.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
frontend/src/app/page.tsx (3)
462-495: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick winClose the map preview with
</Link>, not</div>.Line 462 opens a
Link, but Line 495 closes adiv, which breaks JSX parsing.Suggested fix
- </div> + </Link>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/app/page.tsx` around lines 462 - 495, The map preview block in page.tsx is mismatched: the card starts with a Link but is closed with a div, which breaks JSX. Update the closing tag at the end of this preview section so it matches the opening Link component, keeping the nested Image and overlay divs unchanged.Source: Linters/SAST tools
643-681: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick winReplace the stray
</footer>inProcessCard.
ProcessCardopensdivelements only; the</footer>at Line 678 makes the component invalid JSX.Suggested fix
- </footer> + </div> </div>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/app/page.tsx` around lines 643 - 681, ProcessCard contains an invalid closing tag: it opens only div elements, so the stray </footer> must be removed and the JSX closed with the matching div tags only. Update the ProcessCard component in page.tsx to ensure the wrapper structure around the Image, icon, and number sections is properly balanced and valid JSX.Source: Linters/SAST tools
560-607: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick winFix the workflow section tag balance.
The first section closes before its container
div, and Line 606 adds an extra</div>in the next section.Suggested fix
- </section> + </div> + </section> <section className="py-16 sm:py-20"> ... - </div> </section>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/app/page.tsx` around lines 560 - 607, The section markup in the workflow area is unbalanced: the first section closes before its container div, and there is an extra closing div before the next section. Update the JSX in page.tsx around the “How AirQo AI Works” and “Built for the full decision journey.” blocks so each opening section/div has a matching close, keeping the container hierarchy intact and preserving the workflowSteps map content inside the correct section.Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@frontend/src/app/page.tsx`:
- Around line 501-557: The features block in page.tsx has an unbalanced JSX
structure because the section is closed before the FeatureCard grid starts.
Remove the premature closing tag in the main section and keep the container/div
hierarchy intact so the heading, description, and grid all remain inside the
same section; verify the surrounding JSX around the section that contains
FeatureCard and the text heading is properly nested.
- Around line 357-377: The nearest-country fallback in the page selection logic
should be constrained so it only applies when the visitor is actually within the
monitored footprint. Update the `selectedSites`/`nearestForecastNode` branch in
`page.tsx` to avoid using `getCoordinatesForCountry`, `getDistanceInKm`, and the
`collection.forecasts.reduce` fallback for visitors outside supported coverage,
and keep the existing `visitorCountry` selection behavior intact. Use the
existing `selectedSites`, `selectedCountry`, `visitorCoordinates`, and
`visitorCountry` checks to gate the global nearest-country lookup, so irrelevant
forecasts are not shown.
---
Outside diff comments:
In `@frontend/src/app/page.tsx`:
- Around line 462-495: The map preview block in page.tsx is mismatched: the card
starts with a Link but is closed with a div, which breaks JSX. Update the
closing tag at the end of this preview section so it matches the opening Link
component, keeping the nested Image and overlay divs unchanged.
- Around line 643-681: ProcessCard contains an invalid closing tag: it opens
only div elements, so the stray </footer> must be removed and the JSX closed
with the matching div tags only. Update the ProcessCard component in page.tsx to
ensure the wrapper structure around the Image, icon, and number sections is
properly balanced and valid JSX.
- Around line 560-607: The section markup in the workflow area is unbalanced:
the first section closes before its container div, and there is an extra closing
div before the next section. Update the JSX in page.tsx around the “How AirQo AI
Works” and “Built for the full decision journey.” blocks so each opening
section/div has a matching close, keeping the container hierarchy intact and
preserving the workflowSteps map content inside the correct section.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: bfc5c732-791d-4a7a-bcdb-026423ae5bd3
📒 Files selected for processing (7)
frontend/src/app/categorize/page.tsxfrontend/src/app/locate/page.tsxfrontend/src/app/page.tsxfrontend/src/app/reports/page.tsxfrontend/src/components/map/LeafletMap.tsxfrontend/src/services/apiService.tsxfrontend/src/styles/globals.css
✅ Files skipped from review due to trivial changes (3)
- frontend/src/services/apiService.tsx
- frontend/src/app/categorize/page.tsx
- frontend/src/app/locate/page.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
- frontend/src/components/map/LeafletMap.tsx
- frontend/src/app/reports/page.tsx
- frontend/src/styles/globals.css
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
frontend/src/app/page.tsx (3)
462-495: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick winClose the map preview with
</Link>, not</div>.Line 462 opens a
Link, but Line 495 closes adiv, which breaks JSX parsing.Suggested fix
- </div> + </Link>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/app/page.tsx` around lines 462 - 495, The map preview block in page.tsx is mismatched: the card starts with a Link but is closed with a div, which breaks JSX. Update the closing tag at the end of this preview section so it matches the opening Link component, keeping the nested Image and overlay divs unchanged.Source: Linters/SAST tools
643-681: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick winReplace the stray
</footer>inProcessCard.
ProcessCardopensdivelements only; the</footer>at Line 678 makes the component invalid JSX.Suggested fix
- </footer> + </div> </div>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/app/page.tsx` around lines 643 - 681, ProcessCard contains an invalid closing tag: it opens only div elements, so the stray </footer> must be removed and the JSX closed with the matching div tags only. Update the ProcessCard component in page.tsx to ensure the wrapper structure around the Image, icon, and number sections is properly balanced and valid JSX.Source: Linters/SAST tools
560-607: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick winFix the workflow section tag balance.
The first section closes before its container
div, and Line 606 adds an extra</div>in the next section.Suggested fix
- </section> + </div> + </section> <section className="py-16 sm:py-20"> ... - </div> </section>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/app/page.tsx` around lines 560 - 607, The section markup in the workflow area is unbalanced: the first section closes before its container div, and there is an extra closing div before the next section. Update the JSX in page.tsx around the “How AirQo AI Works” and “Built for the full decision journey.” blocks so each opening section/div has a matching close, keeping the container hierarchy intact and preserving the workflowSteps map content inside the correct section.Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@frontend/src/app/page.tsx`:
- Around line 501-557: The features block in page.tsx has an unbalanced JSX
structure because the section is closed before the FeatureCard grid starts.
Remove the premature closing tag in the main section and keep the container/div
hierarchy intact so the heading, description, and grid all remain inside the
same section; verify the surrounding JSX around the section that contains
FeatureCard and the text heading is properly nested.
- Around line 357-377: The nearest-country fallback in the page selection logic
should be constrained so it only applies when the visitor is actually within the
monitored footprint. Update the `selectedSites`/`nearestForecastNode` branch in
`page.tsx` to avoid using `getCoordinatesForCountry`, `getDistanceInKm`, and the
`collection.forecasts.reduce` fallback for visitors outside supported coverage,
and keep the existing `visitorCountry` selection behavior intact. Use the
existing `selectedSites`, `selectedCountry`, `visitorCoordinates`, and
`visitorCountry` checks to gate the global nearest-country lookup, so irrelevant
forecasts are not shown.
---
Outside diff comments:
In `@frontend/src/app/page.tsx`:
- Around line 462-495: The map preview block in page.tsx is mismatched: the card
starts with a Link but is closed with a div, which breaks JSX. Update the
closing tag at the end of this preview section so it matches the opening Link
component, keeping the nested Image and overlay divs unchanged.
- Around line 643-681: ProcessCard contains an invalid closing tag: it opens
only div elements, so the stray </footer> must be removed and the JSX closed
with the matching div tags only. Update the ProcessCard component in page.tsx to
ensure the wrapper structure around the Image, icon, and number sections is
properly balanced and valid JSX.
- Around line 560-607: The section markup in the workflow area is unbalanced:
the first section closes before its container div, and there is an extra closing
div before the next section. Update the JSX in page.tsx around the “How AirQo AI
Works” and “Built for the full decision journey.” blocks so each opening
section/div has a matching close, keeping the container hierarchy intact and
preserving the workflowSteps map content inside the correct section.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: bfc5c732-791d-4a7a-bcdb-026423ae5bd3
📒 Files selected for processing (7)
frontend/src/app/categorize/page.tsxfrontend/src/app/locate/page.tsxfrontend/src/app/page.tsxfrontend/src/app/reports/page.tsxfrontend/src/components/map/LeafletMap.tsxfrontend/src/services/apiService.tsxfrontend/src/styles/globals.css
✅ Files skipped from review due to trivial changes (3)
- frontend/src/services/apiService.tsx
- frontend/src/app/categorize/page.tsx
- frontend/src/app/locate/page.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
- frontend/src/components/map/LeafletMap.tsx
- frontend/src/app/reports/page.tsx
- frontend/src/styles/globals.css
🛑 Comments failed to post (2)
frontend/src/app/page.tsx (2)
357-377: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Bound the nearest-country forecast fallback.
When
visitorCountryhas no forecast sites, this picks the nearest forecast country globally. For visitors outside the monitored footprint, that can show irrelevant forecasts.Suggested fix
+const NEAREST_FORECAST_FALLBACK_RADIUS_KM = 250 + ... - if (nearestCountry) { + if (nearestCountry && nearestForecastNode.distance <= NEAREST_FORECAST_FALLBACK_RADIUS_KM) { selectedCountry = nearestCountry📝 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.const NEAREST_FORECAST_FALLBACK_RADIUS_KM = 250 if (!selectedSites.length) { const origin = visitorCoordinates || getCoordinatesForCountry(visitorCountry, mapNodes) const nearestForecastNode = origin ? collection.forecasts.reduce<{ node: MapNode; distance: number } | null>((nearest, site) => { const node = nodeBySiteId.get(site.site_details.site_id) if (!node?.siteDetails?.country) return nearest const distance = getDistanceInKm(origin.latitude, origin.longitude, node) if (!Number.isFinite(distance)) return nearest return !nearest || distance < nearest.distance ? { node, distance } : nearest }, null) : null const nearestCountry = nearestForecastNode?.node.siteDetails.country?.trim() if (nearestCountry && nearestForecastNode.distance <= NEAREST_FORECAST_FALLBACK_RADIUS_KM) { selectedCountry = nearestCountry selectedSites = collection.forecasts.filter( (site) => nodeBySiteId.get(site.site_details.site_id)?.siteDetails?.country?.trim().toLowerCase() === nearestCountry.toLowerCase(), ) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/app/page.tsx` around lines 357 - 377, The nearest-country fallback in the page selection logic should be constrained so it only applies when the visitor is actually within the monitored footprint. Update the `selectedSites`/`nearestForecastNode` branch in `page.tsx` to avoid using `getCoordinatesForCountry`, `getDistanceInKm`, and the `collection.forecasts.reduce` fallback for visitors outside supported coverage, and keep the existing `visitorCountry` selection behavior intact. Use the existing `selectedSites`, `selectedCountry`, `visitorCoordinates`, and `visitorCountry` checks to gate the global nearest-country lookup, so irrelevant forecasts are not shown.
501-557: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
Remove the premature
</section>in the features block.Line 510 closes the section before the feature grid, leaving the container/section tags unbalanced.
Suggested fix
</div> - </section> <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">📝 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.<section className="bg-white py-16 dark:bg-slate-950 md:py-24"> <div className="container mx-auto px-4"> <div className="text-center mb-16"> <h2 className="text-3xl md:text-4xl font-bold mb-4">Powered by Artificial Intelligence</h2> <p className="mx-auto max-w-3xl text-lg text-gray-600 dark:text-slate-300"> Our platform leverages cutting-edge AI to provide accurate, real-time air quality data and insights for researchers, policymakers, and citizens. </p> </div> <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8"> <FeatureCard imageSrc="/images/model/locate.webp" Icon={MapPin} title="Optimal Site Location" description="Use AI algorithms to determine the best locations for air quality monitors based on population density, pollution sources, and geographic factors." href="/locate/optimal-site-location" /> <FeatureCard Icon={Wind} title="Air Quality Categorization" description="Automatically categorize monitoring sites based on surrounding land use, traffic patterns, and environmental factors." href="/categorize/site-categorization" imageSrc="/images/model/categorisemap.webp" /> <FeatureCard Icon={BarChart3} title="Data Analytics" description="Generate comprehensive reports with trends, forecasts, and actionable insights from air quality data." href="/analytics/data-analytics" imageSrc="/images/model/analyticsHome.webp" /> <FeatureCard Icon={BrainCircuit} title="Machine Learning Models" description="Continuously improving prediction models that account for seasonal variations, weather patterns, and human activities." href="/models" imageSrc="/images/model/modelapi.webp" /> <FeatureCard Icon={Shield} title="Health Impact Assessment" description="Evaluate potential health impacts of air pollution on different population groups and geographic areas." href="/comingsoon/health-impact" imageSrc="/images/model/calibration-header.webp" /> <FeatureCard Icon={MapPin} title="Interactive Mapping" description="Visualize air quality data across regions with interactive maps showing real-time pollution levels." href="/map/interactive-mapping" imageSrc="/images/homemap.webp" /> </div> </div> </section>🧰 Tools
🪛 Biome (2.5.0)
[error] 502-502: Expected corresponding JSX closing tag for 'div'.
(parse)
[error] 501-501: Expected corresponding JSX closing tag for 'section'.
(parse)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/app/page.tsx` around lines 501 - 557, The features block in page.tsx has an unbalanced JSX structure because the section is closed before the FeatureCard grid starts. Remove the premature closing tag in the main section and keep the container/div hierarchy intact so the heading, description, and grid all remain inside the same section; verify the surrounding JSX around the section that contains FeatureCard and the text heading is properly nested.Source: Linters/SAST tools
…tform/code-samples into feature/admin-controls
Summary by CodeRabbit
Release Notes
New Features
Style
Bug Fixes