Refactor auth session handling and server actions across app

- Overhaul `lib/auth/server.ts` and `lib/auth/session.ts`; update all API
  routes and server actions to use the revised session pattern
- Refactor server actions (settings, titles, watchlist, setup) for
  consistency with new auth layer
- Extract `SetupForm` into its own client component with `useActionState`,
  animated steps, and copyable env snippets
- Move landing page redirect logic into `app/page.tsx`; slim down
  `LandingPage` component
- Add `proxy.ts` for local dev proxying
- Minor cleanup to `NavBar`, `MobileTabBar`, and `TitleCard`
This commit is contained in:
2026-03-06 17:05:49 -05:00
parent 7a3052250d
commit 73b07f5ff6
34 changed files with 443 additions and 466 deletions
-32
View File
@@ -3,22 +3,7 @@
import { motion } from "motion/react";
import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
import { SofaLogo } from "@/components/sofa-logo";
import { useSession } from "@/lib/auth/client";
/** Check whether the server has TMDB configured. */
async function fetchSetupStatus(): Promise<boolean> {
try {
const res = await fetch("/api/setup/status");
if (!res.ok) return true; // assume configured on error
const data = await res.json();
return !!data.tmdbConfigured;
} catch {
return true;
}
}
// Poster positions arranged in angled columns behind the hero
const posterLayout = [
@@ -49,23 +34,6 @@ export function LandingPage({
freshInstall: boolean;
registrationOpen: boolean;
}) {
const { data: session, isPending } = useSession();
const router = useRouter();
useEffect(() => {
if (isPending) return;
if (session?.user) {
router.replace("/dashboard");
return;
}
// If not logged in, check whether TMDB is configured
fetchSetupStatus().then((configured) => {
if (!configured) router.replace("/setup");
});
}, [session, isPending, router]);
if (isPending) return null;
return (
<div className="relative flex min-h-screen flex-col items-center justify-center overflow-hidden">
{/* Background grain texture */}
-4
View File
@@ -4,7 +4,6 @@ import { IconCompass, IconHome, IconSettings } from "@tabler/icons-react";
import { motion } from "motion/react";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useSession } from "@/lib/auth/client";
const tabs = [
{ href: "/dashboard", label: "Home", icon: IconHome },
@@ -13,11 +12,8 @@ const tabs = [
] as const;
export function MobileTabBar() {
const { data: session } = useSession();
const pathname = usePathname();
if (!session?.user) return null;
return (
<nav className="fixed bottom-0 left-0 right-0 z-50 border-t border-border/50 bg-background/90 pl-[env(safe-area-inset-left)] pr-[env(safe-area-inset-right)] backdrop-blur-xl sm:hidden">
<div className="flex h-14 items-stretch">
+3 -4
View File
@@ -14,15 +14,14 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
import { commandPaletteOpenAtom } from "@/lib/atoms/command-palette";
import { signOut, useSession } from "@/lib/auth/client";
import { signOut } from "@/lib/auth/client";
const navLinks = [
{ href: "/dashboard", label: "Home" },
{ href: "/explore", label: "Explore" },
] as const;
export function NavBar() {
const { data: session } = useSession();
export function NavBar({ userName }: { userName: string }) {
const router = useRouter();
const pathname = usePathname();
const setCommandPaletteOpen = useSetAtom(commandPaletteOpenAtom);
@@ -96,7 +95,7 @@ export function NavBar() {
render={<Link href="/settings" />}
className="hidden items-center gap-1.5 rounded-md px-2 py-1.5 text-sm leading-none text-muted-foreground transition-colors hover:text-foreground sm:inline-flex"
>
{session?.user?.name}
{userName}
<IconSettings aria-hidden={true} className="size-3.5" />
</TooltipTrigger>
<TooltipContent>Settings</TooltipContent>
+12 -9
View File
@@ -14,7 +14,7 @@ import {
import { type MotionStyle, type MotionValue, motion } from "motion/react";
import Image from "next/image";
import Link from "next/link";
import { useState } from "react";
import { useEffect, useState } from "react";
import {
Tooltip,
TooltipContent,
@@ -83,8 +83,15 @@ function QuickAddButton({
userStatus ?? null,
);
const effectiveStatus = addedStatus;
const config = effectiveStatus ? statusConfig[effectiveStatus] : null;
// Sync local state when prop changes (e.g. after navigation or SWR revalidation)
useEffect(() => {
if (userStatus) {
setState("added");
setAddedStatus(userStatus);
}
}, [userStatus]);
const config = addedStatus ? statusConfig[addedStatus] : null;
async function handleClick(e: React.MouseEvent) {
e.preventDefault();
@@ -92,13 +99,9 @@ function QuickAddButton({
if (state === "loading" || state === "added") return;
setState("loading");
try {
const result = await quickAddToWatchlist(tmdbId, type);
await quickAddToWatchlist(tmdbId, type);
setState("added");
setAddedStatus(result.alreadyAdded ? null : "watchlist");
if (result.alreadyAdded) {
// Already existed — keep as added but we don't know exact status
setAddedStatus("watchlist");
}
setAddedStatus("watchlist");
} catch {
setState("idle");
}