Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
0671c4a
Migrate Waze API integration from unofficial georss to OpenWeb Ninja
claude Mar 23, 2026
6fd6ec9
Add package-lock.json from npm install
claude Mar 23, 2026
de30648
Add security hardening to Waze API integration
claude Mar 23, 2026
0c52d9b
Comprehensive security hardening for Waze API endpoint
claude Mar 23, 2026
9e25104
Fix: Remove invalid request.ip property for Next.js 16 compatibility
claude Mar 23, 2026
a3e91e5
Update test script to use OpenWeb Ninja API endpoint instead of depre…
claude Mar 23, 2026
2db95c4
Fix: Check shutdown flag at runtime instead of build time to respect …
claude Mar 23, 2026
ca2c098
Fix: Rename proxy.ts to middleware.ts and implement runtime shutdown …
claude Mar 23, 2026
2831ced
Fix: Remove unused startTime and endTime from WazeResponse type to ma…
claude Mar 23, 2026
118da05
Add detailed error logging for OpenWeb Ninja 401 responses
claude Mar 23, 2026
7d488a1
Fix OpenWeb Ninja API endpoint URL
claude Mar 23, 2026
8664968
Improve geolocation error handling and messaging
claude Mar 23, 2026
474e955
Fix Google Fonts import issue
claude Mar 23, 2026
35078c6
Add manual permission request button for geolocation
claude Mar 23, 2026
1d1dfff
Fix TypeScript error in geolocation state update
claude Mar 23, 2026
9a03e68
Add missing permissionDenied property to all setState calls
claude Mar 23, 2026
6f474c4
Add request permission button to loading screen
claude Mar 23, 2026
6c27adf
Add HTTPS check to requestPermission function
claude Mar 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
496 changes: 371 additions & 125 deletions app/api/waze/route.ts

Large diffs are not rendered by default.

10 changes: 2 additions & 8 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import type { Metadata, Viewport } from "next";
import { Geist } from "next/font/google";
import Script from "next/script";
import { PROJECT_SHUTDOWN_ENABLED } from "@/lib/shutdown";
import { Providers } from "./providers";
import "./globals.css";

const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});

const siteUrl = "https://teslanav.com";
const siteName = "TeslaNav";
const siteDescription = "Get Waze-style alerts on your Tesla! TeslaNav brings real-time police alerts, speed camera warnings, accident reports, and road hazard notifications to your Tesla's browser. The best Waze alternative for Tesla owners.";
Expand Down Expand Up @@ -240,8 +234,8 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
const bodyClassName = PROJECT_SHUTDOWN_ENABLED
? `${geistSans.variable} antialiased bg-neutral-950 text-white`
: `${geistSans.variable} antialiased overflow-hidden`;
? `antialiased bg-neutral-950 text-white`
: `antialiased overflow-hidden`;

const bodyStyle = PROJECT_SHUTDOWN_ENABLED
? {
Expand Down
10 changes: 9 additions & 1 deletion app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ function LiveHome() {
const simulationIndexRef = useRef(0);
const simulationIntervalRef = useRef<NodeJS.Timeout | null>(null);

const { latitude: realLatitude, longitude: realLongitude, heading, effectiveHeading: realEffectiveHeading, speed: realSpeed, loading: geoLoading, error: geoError } = useGeolocation();
const { latitude: realLatitude, longitude: realLongitude, heading, effectiveHeading: realEffectiveHeading, speed: realSpeed, loading: geoLoading, error: geoError, requestPermission } = useGeolocation();

// Use simulated position if simulating, otherwise use real position
const latitude = isSimulating && simulatedPosition ? simulatedPosition.lat : realLatitude;
Expand Down Expand Up @@ -921,6 +921,14 @@ function LiveHome() {
<span className="text-gray-400 text-sm font-medium">
{geoError ? geoError : "Finding your location..."}
</span>
{geoError && (
<button
onClick={requestPermission}
className="mt-2 px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-lg text-sm font-medium transition-colors"
>
Try Again
</button>
)}
</div>
</div>
</main>
Expand Down
71 changes: 58 additions & 13 deletions app/record/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ import Link from "next/link";
export default function RecordPage() {
const mapRef = useRef<MapRef>(null);

const {
latitude,
longitude,
effectiveHeading,
const {
latitude,
longitude,
effectiveHeading,
speed,
accuracy,
error: geoError
error: geoError,
permissionDenied,
requestPermission,
} = useGeolocation();

const {
Expand Down Expand Up @@ -72,15 +74,58 @@ export default function RecordPage() {
if (!latitude || !longitude) {
return (
<main className="relative w-full h-full bg-white flex items-center justify-center">
<div className="flex flex-col items-center gap-6">
<div className="flex flex-col items-center gap-3">
<div className="relative">
<div className="w-12 h-12 border-4 border-gray-200 border-t-blue-500 rounded-full animate-spin" />
</div>
<span className="text-gray-600 text-sm font-medium">
{geoError ? geoError : "Finding your location..."}
</span>
<div className="flex flex-col items-center gap-6 px-6 max-w-md">
<div className="flex flex-col items-center gap-4">
{geoError ? (
<>
<div className="text-4xl">📍</div>
<div className="text-center">
<h2 className="text-lg font-semibold text-gray-900 mb-2">Location Error</h2>
<p className="text-gray-600 text-sm mb-4">{geoError}</p>
{permissionDenied && (
<div className="bg-amber-50 border border-amber-200 rounded-lg p-4 mb-4">
<p className="text-sm text-amber-900 mb-3">
Location access was denied. You can request permission again:
</p>
<button
onClick={requestPermission}
className="w-full px-4 py-3 bg-blue-500 hover:bg-blue-600 text-white font-semibold rounded-lg transition-colors"
>
Request Permission
</button>
<p className="text-xs text-amber-800 mt-3">
If prompted, please select "Allow" to grant location access.
</p>
</div>
)}
{!permissionDenied && (
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 text-left text-sm text-blue-900">
<p className="font-semibold mb-2">How to fix:</p>
<ul className="list-disc list-inside space-y-1">
<li>Ensure you're using HTTPS</li>
<li>Check browser location permissions</li>
<li>Allow location access when prompted</li>
<li>Restart your browser and try again</li>
</ul>
</div>
)}
Comment on lines +101 to +111
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add an in-place retry action for non-permission geolocation errors.

Timeout/unavailable errors are often transient, but this branch only shows instructions. Add a retry button here too, so users can recover without navigation.

💡 Suggested fix
 {!permissionDenied && (
   <div className="bg-blue-50 border border-blue-200 rounded-lg p-4 text-left text-sm text-blue-900">
     <p className="font-semibold mb-2">How to fix:</p>
     <ul className="list-disc list-inside space-y-1">
       <li>Ensure you're using HTTPS</li>
       <li>Check browser location permissions</li>
       <li>Allow location access when prompted</li>
       <li>Restart your browser and try again</li>
     </ul>
+    <button
+      onClick={requestPermission}
+      className="mt-4 w-full px-4 py-3 bg-blue-500 hover:bg-blue-600 text-white font-semibold rounded-lg transition-colors"
+    >
+      Try Again
+    </button>
   </div>
 )}
📝 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
{!permissionDenied && (
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 text-left text-sm text-blue-900">
<p className="font-semibold mb-2">How to fix:</p>
<ul className="list-disc list-inside space-y-1">
<li>Ensure you're using HTTPS</li>
<li>Check browser location permissions</li>
<li>Allow location access when prompted</li>
<li>Restart your browser and try again</li>
</ul>
</div>
)}
{!permissionDenied && (
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 text-left text-sm text-blue-900">
<p className="font-semibold mb-2">How to fix:</p>
<ul className="list-disc list-inside space-y-1">
<li>Ensure you're using HTTPS</li>
<li>Check browser location permissions</li>
<li>Allow location access when prompted</li>
<li>Restart your browser and try again</li>
</ul>
<button
onClick={requestPermission}
className="mt-4 w-full px-4 py-3 bg-blue-500 hover:bg-blue-600 text-white font-semibold rounded-lg transition-colors"
>
Try Again
</button>
</div>
)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/record/page.tsx` around lines 101 - 111, The non-permission branch (where
permissionDenied is false) currently only shows static instructions; add an
in-place retry control so transient geolocation errors can be retried without
navigation. Update the JSX block that checks {!permissionDenied} to include an
accessible "Retry" button which invokes the existing geolocation request helper
(e.g., call the component's getLocation / requestLocation / fetchGeolocation
function or create a new handler named retryGeolocation / handleRetry that wraps
the same logic), and wire it to component state for loading/error feedback
(e.g., disable the button while retrying and show a spinner or status). Ensure
the button text and aria attributes are descriptive and reuse the
permissionDenied boolean and existing error state handling to keep behavior
consistent.

</div>
</>
) : (
<>
<div className="relative">
<div className="w-12 h-12 border-4 border-gray-200 border-t-blue-500 rounded-full animate-spin" />
</div>
<span className="text-gray-600 text-sm font-medium">Finding your location...</span>
</>
)}
</div>
<Link
href="/"
className="mt-4 px-4 py-2 bg-gray-200 hover:bg-gray-300 text-gray-800 rounded-lg transition-colors"
>
← Back to Home
</Link>
</div>
</main>
);
Expand Down
79 changes: 76 additions & 3 deletions hooks/useGeolocation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface GeolocationState {
// Calculated/interpolated values for smooth animation
calculatedHeading: number | null;
timestamp: number | null;
permissionDenied: boolean;
}

// Calculate bearing between two points in degrees (0-360, where 0 is north)
Expand Down Expand Up @@ -85,6 +86,7 @@ export function useGeolocation(enableHighAccuracy = true) {
loading: true,
calculatedHeading: null,
timestamp: null,
permissionDenied: false,
});

const [watchId, setWatchId] = useState<number | null>(null);
Expand Down Expand Up @@ -155,14 +157,28 @@ export function useGeolocation(enableHighAccuracy = true) {
loading: false,
calculatedHeading,
timestamp,
permissionDenied: false,
});
}, []);

const handleError = useCallback((error: GeolocationPositionError) => {
let errorMsg = error.message;
const isPermissionDenied = error.code === 1;

// Provide more helpful error messages
if (error.code === 1) {
errorMsg = "Location access denied. Tap 'Request Permission' to try again.";
} else if (error.code === 2) {
errorMsg = "Location unavailable. Please ensure geolocation is enabled.";
} else if (error.code === 3) {
errorMsg = "Location request timed out. Please try again.";
}

setState((prev) => ({
...prev,
error: error.message,
error: errorMsg,
loading: false,
permissionDenied: isPermissionDenied,
}));

// Track geolocation error
Expand All @@ -178,6 +194,20 @@ export function useGeolocation(enableHighAccuracy = true) {
...prev,
error: "Geolocation is not supported",
loading: false,
permissionDenied: false,
}));
return;
}

// Check if page is HTTPS or localhost (geolocation requires secure context)
const isSecureContext = window.location.protocol === 'https:' || window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';

if (!isSecureContext) {
setState((prev) => ({
...prev,
error: "Geolocation requires HTTPS. Please access this site over HTTPS.",
loading: false,
permissionDenied: false,
}));
return;
}
Expand Down Expand Up @@ -209,14 +239,57 @@ export function useGeolocation(enableHighAccuracy = true) {
}
}, [watchId]);

// Request permission manually (for when user taps "Request Permission")
const requestPermission = useCallback(() => {
if (!navigator.geolocation) {
setState((prev) => ({
...prev,
error: "Geolocation is not supported",
loading: false,
permissionDenied: false,
}));
return;
}

// Check if page is HTTPS or localhost (geolocation requires secure context)
const isSecureContext = window.location.protocol === 'https:' || window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';

if (!isSecureContext) {
setState((prev) => ({
...prev,
error: "Geolocation requires HTTPS. Please access this site over HTTPS.",
loading: false,
permissionDenied: false,
}));
return;
}

setState((prev) => ({
...prev,
loading: true,
error: null,
permissionDenied: false,
}));

const options: PositionOptions = {
enableHighAccuracy,
timeout: 10000,
maximumAge: 0,
};

// Request position - this triggers the permission prompt
navigator.geolocation.getCurrentPosition(updatePosition, handleError, options);
}, [enableHighAccuracy, updatePosition, handleError]);
Comment on lines +242 to +268
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify current retry behavior is one-shot and where watchPosition is established.
rg -nP 'const requestPermission|getCurrentPosition|watchPosition|setWatchId' hooks/useGeolocation.ts -C3

Repository: R44VC0RP/teslanav.com

Length of output: 1433


Re-enable continuous tracking after manual permission retry.

The requestPermission function at line 281 only calls getCurrentPosition, so recovery is one-shot. After the user denies permission initially, the watchPosition watcher (line 225) never starts. When the user later grants permission via requestPermission, continuous updates resume only for that single call—the watcher is not restarted, and location updates stop after the first success.

💡 Suggested fix
 const requestPermission = useCallback(() => {
@@
     // Request position - this triggers the permission prompt
-    navigator.geolocation.getCurrentPosition(updatePosition, handleError, options);
-  }, [enableHighAccuracy, updatePosition, handleError]);
+    navigator.geolocation.getCurrentPosition(
+      (position) => {
+        updatePosition(position);
+        // Ensure continuous updates resume after a successful retry
+        if (watchId === null) {
+          const id = navigator.geolocation.watchPosition(updatePosition, handleError, options);
+          setWatchId(id);
+        }
+      },
+      handleError,
+      options
+    );
+  }, [enableHighAccuracy, updatePosition, handleError, watchId]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/useGeolocation.ts` around lines 242 - 268, requestPermission currently
only calls navigator.geolocation.getCurrentPosition, so after a manual retry the
one-shot succeeds but the continuous watcher started at the earlier
watchPosition block never restarts; update requestPermission to, after a
successful getCurrentPosition (i.e. where updatePosition is invoked), start the
continuous watcher the same way the watcher at the watchPosition block does —
either call the existing function that creates the watcher (e.g. startWatching /
startWatch) or invoke navigator.geolocation.watchPosition(updatePosition,
handleError, options) and save its id into the same watchId/state used by the
watcher so continuous updates resume.


// Return the best available heading (GPS heading if moving fast enough, otherwise calculated)
const bestHeading = state.heading !== null && state.speed !== null && state.speed > MIN_SPEED_FOR_HEADING
? state.heading
: state.calculatedHeading;

return {
...state,
return {
...state,
stopWatching,
requestPermission,
// Provide the best heading source
effectiveHeading: bestHeading,
};
Expand Down
Loading