From b2a41db290f88511984337217e225cb64c9c326d Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Thu, 11 Jun 2026 13:42:04 +0200 Subject: [PATCH] feat: rich person detail page with bio, dates, links --- spa/src/lib/api/search.ts | 8 ++++ spa/src/locales/en.json | 10 +++++ spa/src/routes/_app/people.$id.tsx | 69 +++++++++++++++++++++++++++--- 3 files changed, 82 insertions(+), 5 deletions(-) diff --git a/spa/src/lib/api/search.ts b/spa/src/lib/api/search.ts index ab0a458..4e72988 100644 --- a/spa/src/lib/api/search.ts +++ b/spa/src/lib/api/search.ts @@ -45,6 +45,14 @@ export const personDtoSchema = z.object({ 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 diff --git a/spa/src/locales/en.json b/spa/src/locales/en.json index 343d411..cc7d097 100644 --- a/spa/src/locales/en.json +++ b/spa/src/locales/en.json @@ -126,6 +126,16 @@ "searchPlaceholder": "Search entries...", "watchedAgo": "Watched {{when}}" }, + "person": { + "biography": "Biography", + "birthday": "Born", + "deathday": "Died", + "placeOfBirth": "Birthplace", + "alsoKnownAs": "Also Known As", + "links": "Links", + "homepage": "Homepage", + "imdb": "IMDb" + }, "social": { "title": "Social", "following": "Following", diff --git a/spa/src/routes/_app/people.$id.tsx b/spa/src/routes/_app/people.$id.tsx index 7e17800..d0bea00 100644 --- a/spa/src/routes/_app/people.$id.tsx +++ b/spa/src/routes/_app/people.$id.tsx @@ -1,13 +1,15 @@ import { createFileRoute } from "@tanstack/react-router" import { useTranslation } from "react-i18next" -import { Film, User } from "lucide-react" +import { Calendar, ExternalLink, Film, Globe, MapPin, User } from "lucide-react" import { BackButton } from "@/components/back-button" import { MovieCard } from "@/components/movie-card" import { EmptyState } from "@/components/empty-state" +import { Badge } from "@/components/ui/badge" import { Skeleton } from "@/components/ui/skeleton" import { tmdbProfileUrl } from "@/lib/api/client" import { usePersonCredits } from "@/hooks/use-search" import { useDocumentTitle } from "@/hooks/use-document-title" +import { shortDate } from "@/lib/date" export const Route = createFileRoute("/_app/people/$id")({ component: PersonDetailPage, @@ -28,24 +30,80 @@ function PersonDetailPage() {
-
-
+ {/* Header */} +
+
{person.profile_path ? ( ) : (
- +
)}
-
+

{person.name}

{person.known_for_department && (

{person.known_for_department}

)} + {person.birthday && ( +

+ + {shortDate(person.birthday)} + {person.deathday && ` — ${shortDate(person.deathday)}`} +

+ )} + {person.place_of_birth && ( +

+ + {person.place_of_birth} +

+ )}
+ {/* Links */} + {(person.homepage || person.imdb_url) && ( +
+ {person.imdb_url && ( + + + + {t("person.imdb")} + + + )} + {person.homepage && ( + + + + {t("person.homepage")} + + + )} +
+ )} + + {/* Biography */} + {person.biography && ( +

{person.biography}

+ )} + + {/* Also known as */} + {person.also_known_as.length > 0 && ( +
+

{t("person.alsoKnownAs")}

+
+ {person.also_known_as.map((name) => ( + + {name} + + ))} +
+
+ )} + + {/* Cast credits */} {cast.length > 0 && (

@@ -70,6 +128,7 @@ function PersonDetailPage() {

)} + {/* Crew credits */} {crew.length > 0 && (