From 59d9caf7dbd25bf957b22c9ae8a15d2b75ebba57 Mon Sep 17 00:00:00 2001 From: Nicolas Dos Santos <168879994+NewCoder3294@users.noreply.github.com> Date: Tue, 19 May 2026 15:29:47 -0700 Subject: [PATCH 1/2] feat(landing): open the site without login + wire real HLS preview (#13) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove Supabase auth middleware and (app) layout gate; the dashboard is now reachable without sign-in. - Delete /login route, (auth) layout, and /auth/callback handler. - Replace the business-owner landing's "HLS feed preview" placeholder with a real Caltrans D4 stream rendered via the existing LiveStream component (proxied through /api/hls). Falls back to an offline overlay if the upstream feed is unavailable. Supabase client libs are kept — contributor flows still read from them. Co-authored-by: Claude Opus 4.7 (1M context) --- apps/web/app/(app)/layout.tsx | 10 +-- apps/web/app/(auth)/layout.tsx | 17 ----- apps/web/app/(auth)/login/page.tsx | 72 ------------------- apps/web/app/auth/callback/route.ts | 15 ---- .../landing/business-owner-view.tsx | 18 +++-- apps/web/middleware.ts | 56 --------------- 6 files changed, 14 insertions(+), 174 deletions(-) delete mode 100644 apps/web/app/(auth)/layout.tsx delete mode 100644 apps/web/app/(auth)/login/page.tsx delete mode 100644 apps/web/app/auth/callback/route.ts delete mode 100644 apps/web/middleware.ts diff --git a/apps/web/app/(app)/layout.tsx b/apps/web/app/(app)/layout.tsx index c2db356..e4e53bc 100644 --- a/apps/web/app/(app)/layout.tsx +++ b/apps/web/app/(app)/layout.tsx @@ -1,14 +1,6 @@ -import { redirect } from "next/navigation"; -import { createClient } from "@/lib/supabase/server"; import { TopNav } from "@/components/app-shell/top-nav"; -export default async function AppLayout({ children }: { children: React.ReactNode }) { - const supabase = await createClient(); - const { - data: { user }, - } = await supabase.auth.getUser(); - if (!user) redirect("/login"); - +export default function AppLayout({ children }: { children: React.ReactNode }) { return (
diff --git a/apps/web/app/(auth)/layout.tsx b/apps/web/app/(auth)/layout.tsx deleted file mode 100644 index 197d2fd..0000000 --- a/apps/web/app/(auth)/layout.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import Image from "next/image"; - -export default function AuthLayout({ children }: { children: React.ReactNode }) { - return ( -
-
-
- WatchDog - - Real-time crime intelligence - -
- {children} -
-
- ); -} diff --git a/apps/web/app/(auth)/login/page.tsx b/apps/web/app/(auth)/login/page.tsx deleted file mode 100644 index de5300d..0000000 --- a/apps/web/app/(auth)/login/page.tsx +++ /dev/null @@ -1,72 +0,0 @@ -"use client"; - -import { Suspense, useState } from "react"; -import { useRouter, useSearchParams } from "next/navigation"; -import type { Route } from "next"; -import { createClient } from "@/lib/supabase/browser"; - -function LoginForm() { - const router = useRouter(); - const params = useSearchParams(); - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [error, setError] = useState(null); - const [loading, setLoading] = useState(false); - - async function onSubmit(e: React.FormEvent) { - e.preventDefault(); - setLoading(true); - setError(null); - const supabase = createClient(); - const { error } = await supabase.auth.signInWithPassword({ email, password }); - setLoading(false); - if (error) { - setError(error.message); - return; - } - router.replace((params.get("next") ?? "/") as Route); - router.refresh(); - } - - return ( -
-

WatchDog

- - - {error &&

{error}

} - -
- ); -} - -export default function LoginPage() { - return ( - - - - ); -} diff --git a/apps/web/app/auth/callback/route.ts b/apps/web/app/auth/callback/route.ts deleted file mode 100644 index b639332..0000000 --- a/apps/web/app/auth/callback/route.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { NextResponse, type NextRequest } from "next/server"; -import { createClient } from "@/lib/supabase/server"; - -export async function GET(request: NextRequest) { - const { searchParams, origin } = new URL(request.url); - const code = searchParams.get("code"); - const next = searchParams.get("next") ?? "/"; - - if (code) { - const supabase = await createClient(); - await supabase.auth.exchangeCodeForSession(code); - } - - return NextResponse.redirect(`${origin}${next}`); -} diff --git a/apps/web/components/landing/business-owner-view.tsx b/apps/web/components/landing/business-owner-view.tsx index 6aa15f5..9e3b774 100644 --- a/apps/web/components/landing/business-owner-view.tsx +++ b/apps/web/components/landing/business-owner-view.tsx @@ -1,6 +1,12 @@ "use client"; import { useEffect, useRef, useState } from "react"; +import { LiveStream } from "@/components/cameras/live-stream"; + +// Public Caltrans D4 HLS feed — proxied through /api/hls. +// Falls back to an "offline" overlay if the stream is down. +const LANDING_PREVIEW_STREAM = + "https://wzmedia.dot.ca.gov/D4/N101_at_6th.stream/playlist.m3u8"; // IntersectionObserver: returns [ref, inView]. Latches to true so reveal // animations play exactly once when the section scrolls into view. @@ -147,11 +153,13 @@ function OwnerAlertMock() { CAM 14B · live
-
- - ⟶ HLS feed preview - -
+
request.cookies.getAll(), - setAll: ( - cookiesToSet: { name: string; value: string; options: CookieOptions }[], - ) => { - for (const { name, value } of cookiesToSet) { - request.cookies.set(name, value); - } - response = NextResponse.next({ request }); - for (const { name, value, options } of cookiesToSet) { - response.cookies.set(name, value, options); - } - }, - }, - }, - ); - - const { - data: { user }, - } = await supabase.auth.getUser(); - - const { pathname } = request.nextUrl; - const isAuthRoute = pathname.startsWith("/login") || pathname.startsWith("/auth"); - const isPublicRoute = pathname === "/"; - - if (!user && !isAuthRoute && !isPublicRoute) { - const redirect = request.nextUrl.clone(); - redirect.pathname = "/login"; - redirect.searchParams.set("next", pathname); - return NextResponse.redirect(redirect); - } - - if (user && pathname === "/login") { - const redirect = request.nextUrl.clone(); - redirect.pathname = "/"; - return NextResponse.redirect(redirect); - } - - return response; -} - -export const config = { - matcher: [ - "/((?!_next/static|_next/image|favicon.ico|api/cron|api/hls|api/dispatch|api/live|api/openclaw|api/seed|api/contribute|api/contributor-waitlist|c/|contribute|.*\\.[a-zA-Z0-9]+$).*)", - ], -}; From c5d2ef4c884552a912ef6ad10f8c0cd94f996d0a Mon Sep 17 00:00:00 2001 From: Nicolas Gomes Ferreira Dos Santos Date: Tue, 19 May 2026 16:01:57 -0700 Subject: [PATCH 2/2] fix(wall): stop the reshuffle, keep offline tiles in their slot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `hideOffline` defaults back to false. Offline tiles now render the existing "offline" overlay in-place instead of being filtered out of the visible array, which was causing CSS-grid reflow on every status flip — and an empty wall in prod, where many Caltrans HLS URLs are returning 404 (e.g. wzmedia.dot.ca.gov/D4/N101_at_6th.stream/playlist.m3u8). Re-applies the fix from 9672f31, which was reverted by 0010078 without restoring any in-place offline behavior. URL-rot itself still needs a separate pass — sync-cameras + scheduling validate-cameras in vercel.json — but the wall stops looking broken in the meantime. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/web/components/cameras/camera-wall.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/web/components/cameras/camera-wall.tsx b/apps/web/components/cameras/camera-wall.tsx index 7a59b83..4ed4926 100644 --- a/apps/web/components/cameras/camera-wall.tsx +++ b/apps/web/components/cameras/camera-wall.tsx @@ -26,7 +26,12 @@ export function CameraWall({ cameras }: Props) { const [stream, setStream] = useState("hls"); const [query, setQuery] = useState(""); const [visibleCount, setVisibleCount] = useState(PAGE_SIZE); - const [hideOffline, setHideOffline] = useState(true); + // Default OFF: offline tiles stay in their slot with the "offline" overlay + // instead of being filtered out of `visible`, which triggers CSS-grid + // reflow and visually shifts every surviving tile. Re-applies the fix + // from 9672f31, which was reverted in 0010078 without restoring any + // in-place behavior. + const [hideOffline, setHideOffline] = useState(false); const [offlineIds, setOfflineIds] = useState>(() => new Set()); const reportStatus = useCallback((id: string, status: CameraStatus) => {