- merge lib/api/* + hooks/use-* → features/* domain modules - log-sheet + edit-review-sheet → review-sheet with mode discriminant - index.tsx → feed-tab, watchlist-tab, queue-tab - social actor-list pattern → ActorList component - movie detail inline sections → community-reviews, viewing-history
146 lines
4.3 KiB
TypeScript
146 lines
4.3 KiB
TypeScript
import { z } from "zod"
|
|
import { useInfiniteQuery, useQuery } from "@tanstack/react-query"
|
|
import { paginatedSchema } from "@/lib/api/common"
|
|
import { get } from "@/lib/api/client"
|
|
|
|
const PAGE_SIZE = 20
|
|
|
|
export const searchQueryParamsSchema = z.object({
|
|
q: z.string().optional(),
|
|
genre: z.string().optional(),
|
|
year: z.number().optional(),
|
|
person_id: z.string().uuid().optional(),
|
|
department: z.string().optional(),
|
|
language: z.string().optional(),
|
|
limit: z.number().optional(),
|
|
offset: z.number().optional(),
|
|
})
|
|
export type SearchQueryParams = z.infer<typeof searchQueryParamsSchema>
|
|
|
|
export const movieSearchHitDtoSchema = z.object({
|
|
movie_id: z.string().uuid(),
|
|
title: z.string(),
|
|
release_year: z.number().optional(),
|
|
director: z.string().optional(),
|
|
poster_path: z.string().optional(),
|
|
genres: z.array(z.string()),
|
|
})
|
|
export type MovieSearchHitDto = z.infer<typeof movieSearchHitDtoSchema>
|
|
|
|
export const personSearchHitDtoSchema = z.object({
|
|
person_id: z.string().uuid(),
|
|
name: z.string(),
|
|
known_for_department: z.string().optional(),
|
|
profile_path: z.string().optional(),
|
|
known_for_titles: z.array(z.string()),
|
|
})
|
|
export type PersonSearchHitDto = z.infer<typeof personSearchHitDtoSchema>
|
|
|
|
export const searchResponseSchema = z.object({
|
|
movies: paginatedSchema(movieSearchHitDtoSchema),
|
|
people: paginatedSchema(personSearchHitDtoSchema),
|
|
})
|
|
export type SearchResponse = z.infer<typeof searchResponseSchema>
|
|
|
|
export const personDtoSchema = z.object({
|
|
id: z.string().uuid(),
|
|
external_id: z.string(),
|
|
name: z.string(),
|
|
known_for_department: z.string().optional(),
|
|
profile_path: z.string().optional(),
|
|
biography: z.string().optional(),
|
|
birthday: z.string().optional(),
|
|
deathday: z.string().optional(),
|
|
place_of_birth: z.string().optional(),
|
|
also_known_as: z.array(z.string()).default([]),
|
|
homepage: z.string().optional(),
|
|
imdb_url: z.string().optional(),
|
|
enriched: z.boolean().default(false),
|
|
})
|
|
export type PersonDto = z.infer<typeof personDtoSchema>
|
|
|
|
export const castCreditDtoSchema = z.object({
|
|
movie_id: z.string().uuid(),
|
|
title: z.string(),
|
|
release_year: z.number().optional(),
|
|
character: z.string(),
|
|
poster_path: z.string().optional(),
|
|
})
|
|
export type CastCreditDto = z.infer<typeof castCreditDtoSchema>
|
|
|
|
export const crewCreditDtoSchema = z.object({
|
|
movie_id: z.string().uuid(),
|
|
title: z.string(),
|
|
release_year: z.number().optional(),
|
|
job: z.string(),
|
|
department: z.string(),
|
|
poster_path: z.string().optional(),
|
|
})
|
|
export type CrewCreditDto = z.infer<typeof crewCreditDtoSchema>
|
|
|
|
export const personCreditsDtoSchema = z.object({
|
|
person: personDtoSchema,
|
|
cast: z.array(castCreditDtoSchema),
|
|
crew: z.array(crewCreditDtoSchema),
|
|
})
|
|
export type PersonCreditsDto = z.infer<typeof personCreditsDtoSchema>
|
|
|
|
function search(params: SearchQueryParams) {
|
|
return get<SearchResponse>("/search", params)
|
|
}
|
|
|
|
function getPerson(id: string) {
|
|
return get<PersonDto>(`/people/${id}`)
|
|
}
|
|
|
|
function getPersonCredits(id: string) {
|
|
return get<PersonCreditsDto>(`/people/${id}/credits`)
|
|
}
|
|
|
|
export const searchKeys = {
|
|
all: ["search"] as const,
|
|
query: (params: SearchQueryParams) => [...searchKeys.all, params] as const,
|
|
person: (id: string) => ["people", id] as const,
|
|
personCredits: (id: string) => ["people", id, "credits"] as const,
|
|
}
|
|
|
|
export function useSearch(params: SearchQueryParams) {
|
|
return useQuery({
|
|
queryKey: searchKeys.query(params),
|
|
queryFn: () => search(params),
|
|
enabled: !!params.q || !!params.genre || !!params.person_id,
|
|
})
|
|
}
|
|
|
|
export function useInfiniteSearch(
|
|
params: Omit<SearchQueryParams, "limit" | "offset">,
|
|
) {
|
|
return useInfiniteQuery({
|
|
queryKey: searchKeys.query(params),
|
|
queryFn: ({ pageParam = 0 }) =>
|
|
search({ ...params, limit: PAGE_SIZE, offset: pageParam }),
|
|
initialPageParam: 0,
|
|
getNextPageParam: (last) => {
|
|
const next = last.movies.offset + last.movies.limit
|
|
return next < last.movies.total_count ? next : undefined
|
|
},
|
|
enabled: !!params.q || !!params.genre || !!params.person_id,
|
|
})
|
|
}
|
|
|
|
export function usePerson(id: string) {
|
|
return useQuery({
|
|
queryKey: searchKeys.person(id),
|
|
queryFn: () => getPerson(id),
|
|
enabled: !!id,
|
|
})
|
|
}
|
|
|
|
export function usePersonCredits(id: string) {
|
|
return useQuery({
|
|
queryKey: searchKeys.personCredits(id),
|
|
queryFn: () => getPersonCredits(id),
|
|
enabled: !!id,
|
|
})
|
|
}
|