mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-08-29 01:35:39 -04:00
Add instant navigation from search results via TMDB IDs in URL
Navigate instantly to /titles/tmdb-{id}-{type} and show a skeleton
while the new /api/titles/resolve endpoint handles the full import
(TMDB fetch, availability, recommendations, colors). Existing titles
resolve in ~5ms; the URL is replaced with the UUID once resolved.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -69,7 +69,6 @@ export function CommandPalette() {
|
||||
const [query, setQuery] = useState("");
|
||||
const [results, setResults] = useState<SearchResult[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [importing, setImporting] = useState<number | null>(null);
|
||||
const [recentSearches, setRecentSearches] = useState<string[]>([]);
|
||||
const debouncedQuery = useDebounce(query, 300);
|
||||
|
||||
@@ -135,22 +134,9 @@ export function CommandPalette() {
|
||||
}, [debouncedQuery]);
|
||||
|
||||
const handleSelect = useCallback(
|
||||
async (result: SearchResult) => {
|
||||
setImporting(result.tmdbId);
|
||||
try {
|
||||
const res = await fetch("/api/titles/import", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ tmdbId: result.tmdbId, type: result.type }),
|
||||
});
|
||||
const title = await res.json();
|
||||
if (title.id) {
|
||||
setCommandPaletteOpen(false);
|
||||
router.push(`/titles/${title.id}`);
|
||||
}
|
||||
} finally {
|
||||
setImporting(null);
|
||||
}
|
||||
(result: SearchResult) => {
|
||||
setCommandPaletteOpen(false);
|
||||
router.push(`/titles/tmdb-${result.tmdbId}-${result.type}`);
|
||||
},
|
||||
[router, setCommandPaletteOpen],
|
||||
);
|
||||
@@ -205,7 +191,6 @@ export function CommandPalette() {
|
||||
<CommandItem
|
||||
key={`${r.type}-${r.tmdbId}`}
|
||||
onSelect={() => handleSelect(r)}
|
||||
disabled={importing === r.tmdbId}
|
||||
className="flex items-center gap-3 py-2"
|
||||
>
|
||||
<div className="h-12 w-8 shrink-0 overflow-hidden rounded bg-muted">
|
||||
@@ -237,9 +222,6 @@ export function CommandPalette() {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{importing === r.tmdbId && (
|
||||
<div className="h-3.5 w-3.5 animate-spin rounded-full border-2 border-primary border-t-transparent" />
|
||||
)}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
|
||||
@@ -34,7 +34,6 @@ export function SearchAutocomplete({
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [filter, setFilter] = useState<"all" | "movie" | "tv">("all");
|
||||
const [importing, setImporting] = useState<number | null>(null);
|
||||
const debouncedQuery = useDebounce(query, 300);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
@@ -79,25 +78,9 @@ export function SearchAutocomplete({
|
||||
};
|
||||
}, [debouncedQuery, filter, onResults, onLoading]);
|
||||
|
||||
const handleImport = useCallback(
|
||||
async (result: SearchResult) => {
|
||||
setImporting(result.tmdbId);
|
||||
try {
|
||||
const res = await fetch("/api/titles/import", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ tmdbId: result.tmdbId, type: result.type }),
|
||||
});
|
||||
const title = await res.json();
|
||||
if (title.id) {
|
||||
toast.success(`Added "${result.title}" to library`);
|
||||
router.push(`/titles/${title.id}`);
|
||||
}
|
||||
} catch {
|
||||
toast.error("Failed to import title");
|
||||
} finally {
|
||||
setImporting(null);
|
||||
}
|
||||
const handleSelect = useCallback(
|
||||
(result: SearchResult) => {
|
||||
router.push(`/titles/tmdb-${result.tmdbId}-${result.type}`);
|
||||
},
|
||||
[router],
|
||||
);
|
||||
@@ -159,7 +142,7 @@ export function SearchAutocomplete({
|
||||
<CommandPrimitive.Item
|
||||
key={`${r.type}-${r.tmdbId}`}
|
||||
value={`${r.type}-${r.tmdbId}`}
|
||||
onSelect={() => handleImport(r)}
|
||||
onSelect={() => handleSelect(r)}
|
||||
className="flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2.5 text-sm data-[selected=true]:bg-accent"
|
||||
>
|
||||
<div className="h-[60px] w-10 shrink-0 overflow-hidden rounded-md bg-muted">
|
||||
@@ -200,9 +183,6 @@ export function SearchAutocomplete({
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{importing === r.tmdbId && (
|
||||
<div className="h-4 w-4 shrink-0 animate-spin rounded-full border-2 border-primary border-t-transparent" />
|
||||
)}
|
||||
</CommandPrimitive.Item>
|
||||
))}
|
||||
</CommandPrimitive.List>
|
||||
|
||||
Reference in New Issue
Block a user