Replace tmdb-* URL routing with resolveTitle/resolvePerson server actions

- Add `resolveTitle` and `resolvePerson` server actions that import
  from TMDB and return the internal DB id
- Remove `tmdb-{id}-{type}` URL pattern and server-side redirect logic
  from TitleDetailPage and PersonDetailPage
- Rename `importTitle` → `getOrFetchTitleByTmdbId`; add `getOrFetchTitle`
  combining fetch + children lookup
- Update HeroBanner, TitleCard, and CommandPalette to call resolve
  actions client-side before pushing to router
- Batch availability offer inserts into a single transaction
This commit is contained in:
2026-03-07 14:27:03 -05:00
parent ccd482db4e
commit 8a45aa1c36
17 changed files with 552 additions and 419 deletions
+8 -2
View File
@@ -36,6 +36,8 @@ import { Kbd } from "@/components/ui/kbd";
import { Skeleton } from "@/components/ui/skeleton";
import { useDebounce } from "@/hooks/use-debounce";
import { useSearch } from "@/hooks/use-search";
import { resolvePerson } from "@/lib/actions/people";
import { resolveTitle } from "@/lib/actions/titles";
import {
commandPaletteOpenAtom,
helpOpenAtom,
@@ -118,9 +120,13 @@ export function CommandPalette() {
setCommandPaletteOpen(false);
progress.start();
if (result.type === "person") {
router.push(`/people/tmdb-${result.tmdbId}`);
void resolvePerson(result.tmdbId).then((id) => {
if (id) router.push(`/people/${id}`);
});
} else {
router.push(`/titles/tmdb-${result.tmdbId}-${result.type}`);
void resolveTitle(result.tmdbId, result.type).then((id) => {
if (id) router.push(`/titles/${id}`);
});
}
},
[router, setCommandPaletteOpen, progress],
+47 -23
View File
@@ -14,13 +14,16 @@ import {
import { type MotionStyle, type MotionValue, motion } from "motion/react";
import Image from "next/image";
import Link from "next/link";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { useEffect, useState, useTransition } from "react";
import { useProgress } from "@/components/navigation-progress";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { useTiltEffect } from "@/hooks/use-tilt-effect";
import { resolveTitle } from "@/lib/actions/titles";
import { quickAddToWatchlist } from "@/lib/actions/watchlist";
type TitleStatus = "watchlist" | "in_progress" | "completed";
@@ -280,8 +283,30 @@ export function TitleCard({
userStatus,
episodeProgress,
}: TitleCardProps) {
const href = id ? `/titles/${id}` : `/titles/tmdb-${tmdbId}-${type}`;
const tilt = useTiltEffect();
const router = useRouter();
const progress = useProgress();
const [isPending, startTransition] = useTransition();
const cardContent = (
<motion.div ref={tilt.ref} style={tilt.containerStyle} {...tilt.handlers}>
<CardInner
title={title}
type={type}
posterPath={posterPath}
releaseDate={releaseDate}
voteAverage={voteAverage}
userStatus={userStatus}
episodeProgress={episodeProgress}
tiltStyles={{
imageStyle: tilt.imageStyle,
glareBackground: tilt.glareBackground,
glareOpacity: tilt.glareOpacity,
}}
/>
</motion.div>
);
return (
<div className="group relative">
<QuickAddButton
@@ -289,28 +314,27 @@ export function TitleCard({
type={type as "movie" | "tv"}
userStatus={userStatus}
/>
<Link href={href}>
<motion.div
ref={tilt.ref}
style={tilt.containerStyle}
{...tilt.handlers}
{id ? (
<Link href={`/titles/${id}`}>{cardContent}</Link>
) : (
<button
type="button"
disabled={isPending}
className={`w-full text-left ${isPending ? "pointer-events-none opacity-70" : "cursor-pointer"}`}
onClick={() => {
progress.start();
startTransition(async () => {
const resolvedId = await resolveTitle(
tmdbId,
type as "movie" | "tv",
);
if (resolvedId) router.push(`/titles/${resolvedId}`);
});
}}
>
<CardInner
title={title}
type={type}
posterPath={posterPath}
releaseDate={releaseDate}
voteAverage={voteAverage}
userStatus={userStatus}
episodeProgress={episodeProgress}
tiltStyles={{
imageStyle: tilt.imageStyle,
glareBackground: tilt.glareBackground,
glareOpacity: tilt.glareOpacity,
}}
/>
</motion.div>
</Link>
{cardContent}
</button>
)}
</div>
);
}