89 lines
2.6 KiB
TypeScript
89 lines
2.6 KiB
TypeScript
"use client";
|
|
|
|
/**
|
|
* ProfileHeader — Instagram-style centered header used at the top of
|
|
* /u/me and /u/[handle]. Avatar, display name, @handle, a single stat
|
|
* line, and an optional action slot (Edit profile button on owner view).
|
|
*
|
|
* Replaces the older ProfileViewHeader (left-aligned avatar + sidebar).
|
|
*/
|
|
|
|
import Image from "next/image";
|
|
import Link from "next/link";
|
|
|
|
export function ProfileHeader({
|
|
avatarURL,
|
|
name,
|
|
handle,
|
|
postCount,
|
|
joinedAt,
|
|
editHref,
|
|
}: {
|
|
avatarURL: string | null;
|
|
name: string;
|
|
handle: string;
|
|
postCount?: number;
|
|
joinedAt?: string;
|
|
/** When provided, shows an "Edit profile" CTA below the handle. */
|
|
editHref?: string;
|
|
}) {
|
|
return (
|
|
<header className="flex flex-col items-center text-center gap-4 pt-4 pb-6 sm:pt-6 sm:pb-8">
|
|
<Avatar src={avatarURL} name={name} />
|
|
<div className="space-y-1">
|
|
<h1 className="font-[family-name:var(--font-title)] font-semibold text-xl sm:text-2xl text-foreground leading-tight">
|
|
{name}
|
|
</h1>
|
|
<p className="text-sm text-muted-foreground">@{handle}</p>
|
|
{typeof postCount === "number" || joinedAt ? (
|
|
<p className="text-xs text-muted-foreground">
|
|
{typeof postCount === "number" ? (
|
|
<>
|
|
<span className="tabular-nums">{postCount}</span> post
|
|
{postCount === 1 ? "" : "s"}
|
|
</>
|
|
) : null}
|
|
{typeof postCount === "number" && joinedAt ? " · " : null}
|
|
{joinedAt ? <>Joined {new Date(joinedAt).toLocaleDateString()}</> : null}
|
|
</p>
|
|
) : null}
|
|
</div>
|
|
{editHref ? (
|
|
<Link
|
|
href={editHref}
|
|
className="inline-flex items-center justify-center h-9 px-5 rounded-lg border border-border text-sm font-medium text-foreground hover:bg-muted transition-colors"
|
|
>
|
|
Edit profile
|
|
</Link>
|
|
) : null}
|
|
</header>
|
|
);
|
|
}
|
|
|
|
function Avatar({ src, name }: { src: string | null; name: string }) {
|
|
if (src) {
|
|
return (
|
|
<Image
|
|
src={src}
|
|
alt={name}
|
|
width={112}
|
|
height={112}
|
|
className="size-24 sm:size-28 rounded-full object-cover ring-1 ring-foreground/10"
|
|
unoptimized
|
|
/>
|
|
);
|
|
}
|
|
const initials =
|
|
name
|
|
.split(/\s+/)
|
|
.map((p) => p[0])
|
|
.slice(0, 2)
|
|
.join("")
|
|
.toUpperCase() || "?";
|
|
return (
|
|
<div className="size-24 sm:size-28 rounded-full bg-primary/15 text-primary flex items-center justify-center text-3xl font-semibold ring-1 ring-foreground/10">
|
|
{initials}
|
|
</div>
|
|
);
|
|
}
|