Add person detail page and link people tiles to it
GET /api/people/:id returns a published person's profile, public roles (current and past), published events they were billed at or hosted, and public awards, each under the same visibility rules its own page applies. public_phone is never sent. PersonDetail renders it at /people/:id, the route personHref already pointed at. PeopleTiles links a tile with a people id to that page; an expandable tile keeps its details panel and the panel carries a "View full profile" link instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
300c9b74f0
commit
6ec2fb240a
6 changed files with 643 additions and 6 deletions
|
|
@ -123,10 +123,16 @@
|
|||
text-align: inherit;
|
||||
}
|
||||
|
||||
.pl__tile--button {
|
||||
.pl__tile--button,
|
||||
.pl__tile--link {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.pl__tile--link {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.pl__frame {
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
|
@ -150,16 +156,20 @@
|
|||
}
|
||||
|
||||
.pl__tile--button:hover .pl__frame,
|
||||
.pl__tile--button:focus-visible .pl__frame {
|
||||
.pl__tile--button:focus-visible .pl__frame,
|
||||
.pl__tile--link:hover .pl__frame,
|
||||
.pl__tile--link:focus-visible .pl__frame {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 1px 1px rgba(15, 23, 42, 0.06), 0 16px 24px -16px rgba(15, 23, 42, 0.6);
|
||||
}
|
||||
|
||||
.pl__tile--button:focus-visible {
|
||||
.pl__tile--button:focus-visible,
|
||||
.pl__tile--link:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.pl__tile--button:focus-visible .pl__frame {
|
||||
.pl__tile--button:focus-visible .pl__frame,
|
||||
.pl__tile--link:focus-visible .pl__frame {
|
||||
outline: 2px solid var(--pl-accent);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
|
@ -311,6 +321,20 @@
|
|||
max-width: 62ch;
|
||||
}
|
||||
|
||||
.pl__profile {
|
||||
display: inline-block;
|
||||
margin-top: 0.75rem;
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 600;
|
||||
color: var(--pl-accent);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.pl__profile:hover,
|
||||
.pl__profile:focus-visible {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.pl__empty {
|
||||
margin: 0;
|
||||
font-size: 0.9375rem;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,10 @@ import {
|
|||
type HTMLAttributes,
|
||||
} from "react";
|
||||
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import { get } from "../lib/api.js";
|
||||
import { isBadId, personHref } from "../lib/hrefs.ts";
|
||||
import "./PeopleTiles.css";
|
||||
|
||||
/**
|
||||
|
|
@ -43,6 +46,13 @@ import "./PeopleTiles.css";
|
|||
*
|
||||
* Field names follow the API (is_owner, location_label), so a row
|
||||
* from /api/teams/:id/people drops in unchanged.
|
||||
*
|
||||
* Profiles
|
||||
* A person with a string id is taken to be a people row and links
|
||||
* to /people/:id. A tile with nothing to expand is that link; an
|
||||
* expandable one stays the button that opens its panel — a link
|
||||
* can't sit inside a button — and the panel carries the link
|
||||
* instead. A hand-written entry with no id links nowhere.
|
||||
*/
|
||||
|
||||
export interface Person {
|
||||
|
|
@ -551,7 +561,14 @@ function Tile({
|
|||
);
|
||||
|
||||
if (!expandable) {
|
||||
return <div className="pl__tile">{content}</div>;
|
||||
const href = profileHref(person);
|
||||
return href ? (
|
||||
<Link to={href} className="pl__tile pl__tile--link">
|
||||
{content}
|
||||
</Link>
|
||||
) : (
|
||||
<div className="pl__tile">{content}</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -610,6 +627,7 @@ function DetailPanel({
|
|||
const title = titleOf(person);
|
||||
const paragraphs = Array.isArray(person.bio) ? person.bio : [person.bio];
|
||||
const tint = person.accent || group?.accent;
|
||||
const profile = profileHref(person);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -672,6 +690,12 @@ function DetailPanel({
|
|||
{paragraph}
|
||||
</p>
|
||||
))}
|
||||
|
||||
{profile && (
|
||||
<Link to={profile} className="pl__profile">
|
||||
View full profile →
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -693,6 +717,12 @@ function Chevron() {
|
|||
|
||||
/* ── Helpers ─────────────────────────────────────────────────── */
|
||||
|
||||
/* A string id is a people slug; a numeric or missing one is a
|
||||
hand-written entry with no page behind it. */
|
||||
function profileHref(person: Person): string | null {
|
||||
return typeof person.id === "string" && !isBadId(person.id) ? personHref(person.id) : null;
|
||||
}
|
||||
|
||||
function keyFor(group: PeopleGroup, person: Person, index: number): string {
|
||||
return `${group.id}:${person.id ?? person.name ?? index}`;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue