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 4c1fe67b640f41881a6db23c27e091cbaa1941a6 Mon Sep 17 00:00:00 2001 From: Nicolas Gomes Ferreira Dos Santos Date: Tue, 19 May 2026 16:05:41 -0700 Subject: [PATCH 2/2] =?UTF-8?q?fix(probe):=20raise=20abortThreshold=200.5?= =?UTF-8?q?=20=E2=86=92=200.95=20so=20dead=20URLs=20get=20deactivated?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Caltrans D4's CDN routinely 404s 60–80% of the stream URLs it advertises in cctvStatusD04.json. The 0.5 abortThreshold treated that as a transient outage and skipped every write, so dead URLs accumulated indefinitely and /wall went empty in prod. 0.95 still catches a near-total worker egress failure (every probe times out) but lets the daily sync deactivate the actual rot. Cameras that come back online are still revived on the next run via the existing toRevive path, so the change is reversible per-camera. Recommended follow-up: per-camera consecutive-failure tracking instead of a global ratio. Out of scope here. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/sync/src/probe.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/sync/src/probe.ts b/packages/sync/src/probe.ts index 65cdae1..74df5a9 100644 --- a/packages/sync/src/probe.ts +++ b/packages/sync/src/probe.ts @@ -22,7 +22,12 @@ export interface ProbeResult { const DEFAULTS = { concurrency: 20, timeoutMs: 6000, - abortThreshold: 0.5, + // Caltrans D4's CDN routinely 404s 60–80% of the URLs it advertises in + // cctvStatusD04.json (the syncCameras comment notes the same). At the + // old 0.5 threshold every probe run aborted, dead URLs accumulated, and + // /wall went empty. 0.95 still catches a near-total worker egress outage + // (where 100% fail) but lets the probe deactivate the actual rot. + abortThreshold: 0.95, } as const; async function probeOne(