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/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) => { 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]+$).*)", - ], -};