Skip to content

feat: DR-7743 global live activity page#7718

Open
AmanVarshney01 wants to merge 2 commits intomainfrom
dr-7743-global
Open

feat: DR-7743 global live activity page#7718
AmanVarshney01 wants to merge 2 commits intomainfrom
dr-7743-global

Conversation

@AmanVarshney01
Copy link
Member

@AmanVarshney01 AmanVarshney01 commented Mar 27, 2026

Summary

  • Adds /global page to apps/site showing real-time Prisma Accelerate traffic on a world map
  • API route at /api/worldmap proxies analytics from the Prisma analytics exporter, transforms coordinates, and merges with airport POP data
  • Interactive map with animated pulsating markers sized by traffic ratio, tooltips on hover, and active/inactive legend
  • Polls live data every 60 seconds
  • Hero with CTA buttons for Accelerate and Prisma Postgres
  • Social share links (LinkedIn, X, Bluesky)
  • Light/dark mode friendly using design tokens

Summary by CodeRabbit

  • New Features

    • Global Traffic page with an interactive world map showing realtime analytics
    • Map markers auto-refresh every 60 seconds and display hover tooltips with details
    • Active markers visually emphasized; marker sizes reflect activity levels
    • Social sharing links for LinkedIn, X (Twitter), and Bluesky
  • Style

    • Added a pulsing animation used for active markers to indicate live activity

Add /global page to apps/site with real-time world map showing
Prisma Accelerate traffic. Polls analytics API every 60 seconds,
renders animated markers on SVG map with tooltips, legend, and
social share links.
@vercel
Copy link

vercel bot commented Mar 27, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
blog Ready Ready Preview, Comment Mar 27, 2026 10:23am
docs Ready Ready Preview, Comment Mar 27, 2026 10:23am
eclipse Ready Ready Preview, Comment Mar 27, 2026 10:23am
site Ready Ready Preview, Comment Mar 27, 2026 10:23am

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 27, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2566e483-d440-4e87-9051-72d143a95fc2

📥 Commits

Reviewing files that changed from the base of the PR and between de218df and 642119a.

📒 Files selected for processing (3)
  • apps/site/src/app/global.css
  • apps/site/src/app/global/_components/world-map.tsx
  • apps/site/src/app/global/page.tsx
✅ Files skipped from review due to trivial changes (2)
  • apps/site/src/app/global.css
  • apps/site/src/app/global/page.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/site/src/app/global/_components/world-map.tsx

Walkthrough

Adds a global traffic feature: airport coordinate data, a GET API route that fetches and aligns analytics to airports, a client-side WorldMap component with periodic data fetch and animated markers, a CSS keyframes animation, and a new Global page route with hero and share links.

Changes

Cohort / File(s) Summary
Data & API
apps/site/src/data/airports-code.ts, apps/site/src/app/api/worldmap/route.ts
Added airports dataset and a GET API route that fetches external analytics, validates response, maps entries to airports, computes transformed coordinates, returns aligned results as JSON with Cache-Control, and returns errors for upstream/non-OK responses.
UI Component
apps/site/src/app/global/_components/world-map.tsx
Added client-side WorldMap component that fetches /api/worldmap on mount and every 60s, stores marker data, renders percentage-positioned markers over an SVG/map, shows pulsing active markers with tooltips and inactive static dots, and includes a legend.
Styling
apps/site/src/app/global.css
Added @keyframes pulsate animation (scale + opacity) used by active markers.
Page Route
apps/site/src/app/global/page.tsx
Added GlobalPage Next.js route and exported metadata (title/description); renders hero section, CTAs, the WorldMap component, refresh footnote, and external share links.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main objective: adding a new global live activity page with real-time world map visualization of Prisma Accelerate traffic.

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


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

- Markers start opacity:0, animate in via pulsate
- Inactive markers 8px with no animation
- Legend positioned absolutely: bottom-left on desktop, centered below on mobile
- Map padding/margins matching original CSS
- Button row responsive: column on mobile, row on desktop
- Footnote spacing matching website
@argos-ci
Copy link

argos-ci bot commented Mar 27, 2026

The latest updates on your projects. Learn more about Argos notifications ↗︎

Build Status Details Updated (UTC)
default (Inspect) ✅ No changes detected - Mar 27, 2026, 10:29 AM

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 (6)
apps/site/src/data/airports-code.ts (1)

1-8: Consider exporting a TypeScript type for the airport data shape.

Adding an explicit type export would improve type safety and reusability when this data is consumed elsewhere in the codebase, such as in route.ts.

📝 Proposed type export
+export type Airport = {
+  pop: string;
+  coordinates: {
+    lon: number;
+    lat: number;
+  };
+};
+
-export const airports = [
+export const airports: Airport[] = [
   {
     pop: "ACC",
     coordinates: {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/site/src/data/airports-code.ts` around lines 1 - 8, The airports array
currently has no explicit TypeScript type export; add and export a descriptive
interface (e.g., Airport or AirportData) that defines the shape: a string code
property (pop or better named code), and a coordinates object with lon and lat
as numbers, then annotate and export the airports constant with this type
(export const airports: Airport[] = ...). Update any consumers (like route.ts)
to import the new Airport type when needed to improve type safety and reuse.
apps/site/src/app/global/_components/world-map.tsx (3)

84-86: Prefer data.pop as the React key instead of array index.

Each airport has a unique pop (IATA code), which is a stable identifier. Using it as the key is more semantically correct and helps React optimize reconciliation if the array order ever changes.

🔑 Use stable key
         {points.map((data, idx) => (
-          <Marker key={idx} data={data} />
+          <Marker key={data.pop} data={data} />
         ))}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/site/src/app/global/_components/world-map.tsx` around lines 84 - 86, The
map over points uses the array index as the React key which is unstable; change
the key on the Marker component in the points.map call from key={idx} to
key={data.pop} (or key={`${data.pop}-${idx}`} if you need a safe fallback when
pop might be missing) so each Marker uses the airport's stable IATA identifier;
update the points.map JSX where Marker is rendered to use that property instead
of idx.

59-61: The dataPoints ref is unused and can be removed.

The ref dataPoints is assigned on line 69 but never read anywhere. Only the points state is used for rendering. This is dead code that adds confusion.

🧹 Remove unused ref
 export function WorldMap() {
-  const dataPoints = useRef<DataPoint[]>([]);
   const [points, setPoints] = useState<DataPoint[]>([]);
 
   useEffect(() => {
     const fetchData = async () => {
       try {
         const response = await fetch("/api/worldmap");
         if (!response.ok) return;
         const data: DataPoint[] = await response.json();
-        dataPoints.current = data;
         setPoints(data);
       } catch {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/site/src/app/global/_components/world-map.tsx` around lines 59 - 61, The
WorldMap component declares an unused ref dataPoints (useRef<DataPoint[]>) which
is assigned but never read; remove the dead ref and any assignments to it so
rendering only uses the points state and setPoints. Specifically, delete the
dataPoints variable declaration in WorldMap and remove any lines that write to
dataPoints.current, leaving points and setPoints as the sole source of truth for
map rendering.

28-31: Consider clamping the marker size to prevent extremely large markers.

If ratio is a very large value (e.g., 10), the marker width/height would be 20 * 11 = 220px, which could obscure other markers and look visually broken. A max size limit would make the UI more robust.

📏 Proposed size clamping
           ...(isActive && {
-            width: `${20 * (1 + (data.ratio || 0))}px`,
-            height: `${20 * (1 + (data.ratio || 0))}px`,
+            width: `${Math.min(20 * (1 + (data.ratio || 0)), 60)}px`,
+            height: `${Math.min(20 * (1 + (data.ratio || 0)), 60)}px`,
           }),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/site/src/app/global/_components/world-map.tsx` around lines 28 - 31, The
marker sizing logic using isActive and data.ratio can produce excessively large
markers; replace the raw calculation in the block that sets width/height (the
code using width: `${20 * (1 + (data.ratio || 0))}px`) with a clamped value:
compute baseSize = 20 * (1 + (data.ratio || 0)), then clamp it to a reasonable
min/max (e.g., Math.max(minSize, Math.min(baseSize, maxSize))) and use that
clampedSize for both width and height; update the code in world-map.tsx where
isActive and data.ratio are used to derive marker size so oversized markers are
bounded.
apps/site/src/app/api/worldmap/route.ts (2)

51-58: Redundant transformCoordinates calls for active airports.

Active airports already have their cured_coord computed in cured_data (line 46), but line 55 recomputes it. You could reuse the pre-transformed coordinates from the matching cured_data entry instead.

♻️ Proposed optimization to avoid redundant transformations
     const cured_airport_data = airports.map((airport) => {
       const active = cured_data.find((d) => d?.pop === airport.pop);
       return {
         pop: airport.pop,
-        cured_coord: transformCoordinates(airport.coordinates),
+        cured_coord: active?.cured_coord ?? transformCoordinates(airport.coordinates),
         ...(active && { ratio: active.ratio }),
       };
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/site/src/app/api/worldmap/route.ts` around lines 51 - 58, The map that
builds cured_airport_data recomputes transformCoordinates for airports that
already have a precomputed cured_coord in cured_data; update the mapping logic
in the cured_airport_data computation so that for each airport you find active =
cured_data.find(...) and then set cured_coord to active.cured_coord when active
exists, otherwise call transformCoordinates(airport.coordinates); keep the rest
of the object shape (pop and optional ratio) unchanged and preserve existing
null/undefined checks around active.

7-24: The coordinate zero-check is unnecessarily complex.

The condition coordinates.lon > 0 || coordinates.lon < 0 is mathematically equivalent to coordinates.lon !== 0. The same applies to latitude. This makes the intent harder to understand at a glance.

✨ Simplified coordinate transformation
 function transformCoordinates(coordinates: { lat: number; lon: number }) {
-  let temp_lon = coordinates.lon;
-  if (coordinates.lon > 0 || coordinates.lon < 0) {
-    temp_lon = temp_lon + 180;
-  } else temp_lon = 180;
-  temp_lon = (temp_lon * 100) / 360;
+  // Normalize longitude from [-180, 180] to [0, 100] percentage
+  const temp_lon = ((coordinates.lon + 180) * 100) / 360;
 
-  let temp_lat = coordinates.lat;
-  if (coordinates.lat > 0 || coordinates.lat < 0) {
-    temp_lat = temp_lat * -1 + 90;
-  } else temp_lat = 90;
-  temp_lat = (temp_lat * 100) / 180;
+  // Normalize latitude from [-90, 90] to [0, 100] percentage (inverted for CSS top)
+  const temp_lat = ((90 - coordinates.lat) * 100) / 180;
 
   return {
     lon: Number(temp_lon.toFixed(2)),
     lat: Number(temp_lat.toFixed(2)),
   };
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/site/src/app/api/worldmap/route.ts` around lines 7 - 24, The
conditionals in transformCoordinates use `coordinates.lon > 0 || coordinates.lon
< 0` and the same for lat which is equivalent to `!== 0`; update those checks to
`coordinates.lon !== 0` and `coordinates.lat !== 0` (preserving the existing
branches: add 180 when lon !== 0 else set 180, and invert/add 90 when lat !== 0
else set 90) to make the intent clear and simplify the code while keeping the
same numeric behavior and returned values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/site/src/app/api/worldmap/route.ts`:
- Around line 60-64: The Cache-Control on the NextResponse in route.ts is set to
s-maxage=86400 which prevents CDN refresh for 24h and conflicts with the client
polling every ~60s; update the headers passed to NextResponse.json (the return
block) to use a much shorter s-maxage (e.g., s-maxage=60 or s-maxage=120) and
adjust stale-while-revalidate appropriately (e.g., stale-while-revalidate=30) so
the CDN TTL matches the live polling interval.
- Around line 28-35: The external fetch to ACCELERATE_ANALYTICS in route.ts
needs a timeout to avoid hanging; wrap the fetch in an AbortController (set a
5–10s timeout via setTimeout that calls controller.abort()), pass
controller.signal to the fetch call, clear the timeout after fetch completes,
and catch AbortError to return a bounded response (e.g., NextResponse.json with
a 504/timeout message). Update the fetch invocation that currently uses
fetch(ACCELERATE_ANALYTICS) to accept the controller.signal and add the
abort/cleanup logic around it.

In `@apps/site/src/data/airports-code.ts`:
- Around line 1843-1849: The IAD airport entry (object with pop: "IAD" and
coordinates.lon: -77.455833) has an incorrect latitude of 48.944444; update the
coordinates.lat to the correct Washington Dulles value (approximately 38.95) so
the marker renders in the proper location on the map.

---

Nitpick comments:
In `@apps/site/src/app/api/worldmap/route.ts`:
- Around line 51-58: The map that builds cured_airport_data recomputes
transformCoordinates for airports that already have a precomputed cured_coord in
cured_data; update the mapping logic in the cured_airport_data computation so
that for each airport you find active = cured_data.find(...) and then set
cured_coord to active.cured_coord when active exists, otherwise call
transformCoordinates(airport.coordinates); keep the rest of the object shape
(pop and optional ratio) unchanged and preserve existing null/undefined checks
around active.
- Around line 7-24: The conditionals in transformCoordinates use
`coordinates.lon > 0 || coordinates.lon < 0` and the same for lat which is
equivalent to `!== 0`; update those checks to `coordinates.lon !== 0` and
`coordinates.lat !== 0` (preserving the existing branches: add 180 when lon !==
0 else set 180, and invert/add 90 when lat !== 0 else set 90) to make the intent
clear and simplify the code while keeping the same numeric behavior and returned
values.

In `@apps/site/src/app/global/_components/world-map.tsx`:
- Around line 84-86: The map over points uses the array index as the React key
which is unstable; change the key on the Marker component in the points.map call
from key={idx} to key={data.pop} (or key={`${data.pop}-${idx}`} if you need a
safe fallback when pop might be missing) so each Marker uses the airport's
stable IATA identifier; update the points.map JSX where Marker is rendered to
use that property instead of idx.
- Around line 59-61: The WorldMap component declares an unused ref dataPoints
(useRef<DataPoint[]>) which is assigned but never read; remove the dead ref and
any assignments to it so rendering only uses the points state and setPoints.
Specifically, delete the dataPoints variable declaration in WorldMap and remove
any lines that write to dataPoints.current, leaving points and setPoints as the
sole source of truth for map rendering.
- Around line 28-31: The marker sizing logic using isActive and data.ratio can
produce excessively large markers; replace the raw calculation in the block that
sets width/height (the code using width: `${20 * (1 + (data.ratio || 0))}px`)
with a clamped value: compute baseSize = 20 * (1 + (data.ratio || 0)), then
clamp it to a reasonable min/max (e.g., Math.max(minSize, Math.min(baseSize,
maxSize))) and use that clampedSize for both width and height; update the code
in world-map.tsx where isActive and data.ratio are used to derive marker size so
oversized markers are bounded.

In `@apps/site/src/data/airports-code.ts`:
- Around line 1-8: The airports array currently has no explicit TypeScript type
export; add and export a descriptive interface (e.g., Airport or AirportData)
that defines the shape: a string code property (pop or better named code), and a
coordinates object with lon and lat as numbers, then annotate and export the
airports constant with this type (export const airports: Airport[] = ...).
Update any consumers (like route.ts) to import the new Airport type when needed
to improve type safety and reuse.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9dc6a216-83a7-490e-86e4-86664184ce33

📥 Commits

Reviewing files that changed from the base of the PR and between 6f79795 and de218df.

⛔ Files ignored due to path filters (1)
  • apps/site/public/illustrations/world-map/map.svg is excluded by !**/*.svg
📒 Files selected for processing (5)
  • apps/site/src/app/api/worldmap/route.ts
  • apps/site/src/app/global.css
  • apps/site/src/app/global/_components/world-map.tsx
  • apps/site/src/app/global/page.tsx
  • apps/site/src/data/airports-code.ts

Comment on lines +28 to +35
const response = await fetch(ACCELERATE_ANALYTICS);

if (!response.ok) {
return NextResponse.json(
{ message: "Error fetching analytics" },
{ status: response.status },
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

External fetch lacks a timeout, risking request hangs.

If the analytics endpoint becomes slow or unresponsive, this request will hang indefinitely. Consider adding an AbortController with a reasonable timeout (e.g., 5-10 seconds) to ensure the API route responds in a bounded time.

⏱️ Proposed timeout implementation
 export async function GET() {
   try {
-    const response = await fetch(ACCELERATE_ANALYTICS);
+    const controller = new AbortController();
+    const timeoutId = setTimeout(() => controller.abort(), 10000);
+
+    const response = await fetch(ACCELERATE_ANALYTICS, {
+      signal: controller.signal,
+    });
+    clearTimeout(timeoutId);
 
     if (!response.ok) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/site/src/app/api/worldmap/route.ts` around lines 28 - 35, The external
fetch to ACCELERATE_ANALYTICS in route.ts needs a timeout to avoid hanging; wrap
the fetch in an AbortController (set a 5–10s timeout via setTimeout that calls
controller.abort()), pass controller.signal to the fetch call, clear the timeout
after fetch completes, and catch AbortError to return a bounded response (e.g.,
NextResponse.json with a 504/timeout message). Update the fetch invocation that
currently uses fetch(ACCELERATE_ANALYTICS) to accept the controller.signal and
add the abort/cleanup logic around it.

Comment on lines +60 to +64
return NextResponse.json(cured_airport_data, {
headers: {
"Cache-Control": "s-maxage=86400, stale-while-revalidate=59",
},
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Cache duration of 24 hours conflicts with "live" data polling.

The s-maxage=86400 (24 hours) means CDN-cached responses won't refresh for a full day, which contradicts the client polling every 60 seconds and the "live activity" premise. If live data is important, consider reducing s-maxage to match the polling interval or slightly longer (e.g., s-maxage=60 or s-maxage=120).

🔄 Proposed cache header adjustment
     return NextResponse.json(cured_airport_data, {
       headers: {
-        "Cache-Control": "s-maxage=86400, stale-while-revalidate=59",
+        "Cache-Control": "s-maxage=60, stale-while-revalidate=30",
       },
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/site/src/app/api/worldmap/route.ts` around lines 60 - 64, The
Cache-Control on the NextResponse in route.ts is set to s-maxage=86400 which
prevents CDN refresh for 24h and conflicts with the client polling every ~60s;
update the headers passed to NextResponse.json (the return block) to use a much
shorter s-maxage (e.g., s-maxage=60 or s-maxage=120) and adjust
stale-while-revalidate appropriately (e.g., stale-while-revalidate=30) so the
CDN TTL matches the live polling interval.

Comment on lines +1843 to +1849
{
pop: "IAD",
coordinates: {
lon: -77.455833,
lat: 48.944444,
},
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

IAD (Washington Dulles) latitude appears incorrect.

The latitude 48.944444 would place this marker in Canada, roughly near the US-Canada border. Washington Dulles International Airport is actually located at approximately latitude 38.95. This data error will cause the marker to render in the wrong geographic position on the map.

🗺️ Proposed coordinate fix
   {
     pop: "IAD",
     coordinates: {
       lon: -77.455833,
-      lat: 48.944444,
+      lat: 38.944444,
     },
   },
📝 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
{
pop: "IAD",
coordinates: {
lon: -77.455833,
lat: 48.944444,
},
},
{
pop: "IAD",
coordinates: {
lon: -77.455833,
lat: 38.944444,
},
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/site/src/data/airports-code.ts` around lines 1843 - 1849, The IAD
airport entry (object with pop: "IAD" and coordinates.lon: -77.455833) has an
incorrect latitude of 48.944444; update the coordinates.lat to the correct
Washington Dulles value (approximately 38.95) so the marker renders in the
proper location on the map.

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.

1 participant