'use client' /** * Author detail-page body — identity header (avatar → name → job title → * socials), bio, expertise badges, and a `children` slot for the authored- * content rail. THE one implementation behind the hub's `/authors/[slug]` * page AND embedded author pages (react-embedding-example) — hosts fetch * the `AuthorProfile` however they like and drill it in. * * Embed-readiness contract (same rules as ArticleAuthorByline): * - `Image` renders through the embed shim (plain `` in non-Next * hosts; the real `next/image` once registered at app init). * - Avatar proxying rides the OPTIONAL ambient `ChatRuntime` * (`endpoints.imageProxyUrlPrefix`) — no provider ⇒ raw URL, never * throws. `proxyImageUrl` prop wins over the runtime. * - The authored-content rail is a SLOT (`children`), not a baked-in * fetch: the hub passes its pre-bound `RelatedContentSection` with SSR * `initialItems`; embedders pass the lib section with `apiBaseUrl`. * The view stays data-in, markup-out. * * Heading semantics are the HOST's: the view renders the name as a styled * `

` (like the hub profile header) so a hosting page can own its `

` * (the hub uses an sr-only h1 for crawlers). */ import React from 'react' import Image from '../../embed-shims/next-image' import { cn } from '../../utils/cn' import { formatBioText } from '../../utils/format' import { getProxiedImageUrl } from '../../utils/image-proxy' import { useChatRuntime } from '../../contexts/chat-runtime-context' import { SocialIconRow } from '../social-icon-row' import { StatusBadge } from '../ui/status-badge' import type { AuthorProfile } from '../../types/entity-author' export interface AuthorDetailViewProps { author: AuthorProfile /** Host-injected avatar-URL mapper — wins over the ambient runtime. */ proxyImageUrl?: (url: string) => string /** Authored-content rail (or anything else) rendered below the profile. */ children?: React.ReactNode className?: string } export function AuthorDetailView({ author, proxyImageUrl, children, className, }: AuthorDetailViewProps) { // Optional runtime — null-safe outside a provider (bare embeds render the // raw avatar URL; the hub's app-wide HubRuntimeProvider supplies the proxy). const runtime = useChatRuntime() const proxiedAvatar = author.avatarUrl ? proxyImageUrl ? proxyImageUrl(author.avatarUrl) : (getProxiedImageUrl(author.avatarUrl, { proxyPrefix: runtime?.endpoints.imageProxyUrlPrefix, skipDomains: runtime?.endpoints.imageProxySkipDomains, directHttps: true, }) ?? author.avatarUrl) : null const subtitle = [author.jobTitle, author.company].filter(Boolean).join(' @ ') const bioText = formatBioText(author.about) return (
{/* Identity header: avatar left; name → subtitle → socials stacked right. */}
{proxiedAvatar ? ( {author.fullName} ) : (
{author.fullName.charAt(0).toUpperCase()}
)}

{author.fullName}

{subtitle && (

{subtitle}

)} {author.socialLinks.length > 0 && ( // Ghost compact row (transparent 32px) — metadata, not CTAs. ({ platform: link.platform, href: link.url, label: `${author.fullName}'s ${link.platform}`, }))} /> )}
{bioText && (

{bioText}

)} {author.knowsAbout.length > 0 && (
{author.knowsAbout.map((topic) => ( ))}
)} {children}
) }