import { useEffect, useMemo } from "react"; import { useQuery } from "@tanstack/react-query"; import { AlertCircle, UserRound } from "lucide-react"; import type { UserProfileDailyPoint, UserProfileWindowStats } from "@paperclipai/shared"; import { Link, useParams } from "@/lib/router"; import { userProfilesApi } from "../api/userProfiles"; import { Avatar, AvatarFallback, AvatarImage } from "../components/ui/avatar"; import { EmptyState } from "../components/EmptyState"; import { PageSkeleton } from "../components/PageSkeleton"; import { StatusBadge } from "../components/StatusBadge"; import { useBreadcrumbs } from "../context/BreadcrumbContext"; import { useCompany } from "../context/CompanyContext"; import { queryKeys } from "../lib/queryKeys"; import { formatCents, formatDate, formatNumber, formatShortDate, formatTokens, issueUrl, providerDisplayName, relativeTime, } from "../lib/utils"; const NO_COMPANY = "__none__"; function initials(name: string | null | undefined) { const value = name?.trim() || "User"; const parts = value.split(/\s+/).filter(Boolean); if (parts.length > 1) return `${parts[0]?.[0] ?? ""}${parts[parts.length - 1]?.[0] ?? ""}`.toUpperCase(); return value.slice(0, 2).toUpperCase(); } function totalTokens(stats: Pick) { return stats.inputTokens + stats.cachedInputTokens + stats.outputTokens; } function completionRate(stats: UserProfileWindowStats) { if (stats.touchedIssues === 0) return "0%"; return `${Math.round((stats.completedIssues / stats.touchedIssues) * 100)}%`; } function HeroStat({ label, value, hint }: { label: string; value: string; hint?: string }) { return (
{value}
{label}
{hint ?
{hint}
: null}
); } function WindowColumn({ stats }: { stats: UserProfileWindowStats }) { const tokens = totalTokens(stats); return (

{stats.label}

{completionRate(stats)} done
Tokens {formatTokens(tokens)} Spend {formatCents(stats.costCents)} Created {formatNumber(stats.createdIssues)} Open {formatNumber(stats.assignedOpenIssues)}
); } function Metric({ value, label }: { value: string; label: string }) { return (
{value}
{label}
); } function UsageChart({ points }: { points: UserProfileDailyPoint[] }) { const totals = points.map((point) => totalTokens(point)); const maxTokens = Math.max(1, ...totals); const maxCompleted = Math.max(1, ...points.map((point) => point.completedIssues)); const totalTokensSum = totals.reduce((sum, value) => sum + value, 0); return (

Last 14 days

{formatTokens(totalTokensSum)} tokens total
{points.map((point) => { const tokens = totalTokens(point); const heightPct = tokens === 0 ? 0 : Math.max(2, Math.round((tokens / maxTokens) * 100)); const completedPct = point.completedIssues === 0 ? 0 : Math.max(8, Math.round((point.completedIssues / maxCompleted) * 36)); return (
{completedPct > 0 ? (
) : null}
); })}
{points.map((point, index) => (
{index === 0 || index === 6 || index === 13 ? formatShortDate(point.date) : null}
))}
tokens / day completions
); } interface UsageRow { key: string; label: string; sublabel: string; costCents: number; inputTokens: number; cachedInputTokens: number; outputTokens: number; } function UsageList({ title, empty, rows, }: { title: string; empty: string; rows: UsageRow[]; }) { return (

{title}

{rows.length}
{rows.length === 0 ? (
{empty}
) : ( )}
); } export function UserProfile() { const { userSlug = "" } = useParams<{ userSlug: string }>(); const { selectedCompanyId } = useCompany(); const { setBreadcrumbs } = useBreadcrumbs(); const companyId = selectedCompanyId ?? NO_COMPANY; const { data, isLoading, error } = useQuery({ queryKey: queryKeys.userProfile(companyId, userSlug), queryFn: () => userProfilesApi.get(companyId, userSlug), enabled: !!selectedCompanyId && !!userSlug, }); useEffect(() => { setBreadcrumbs([{ label: "Users" }, { label: data?.user.name ?? userSlug }]); }, [data?.user.name, setBreadcrumbs, userSlug]); const allTime = data?.stats.find((entry) => entry.key === "all"); const last7 = data?.stats.find((entry) => entry.key === "last7"); const displayName = data?.user.name?.trim() || data?.user.email?.split("@")[0] || "User"; const agentUsageRows = useMemo( () => (data?.topAgents ?? []).map((row) => ({ key: row.agentId ?? "unknown", label: row.agentName ?? (row.agentId ? row.agentId.slice(0, 8) : "unknown"), sublabel: "Issue-linked usage", costCents: row.costCents, inputTokens: row.inputTokens, cachedInputTokens: row.cachedInputTokens, outputTokens: row.outputTokens, })), [data?.topAgents], ); const providerUsageRows = useMemo( () => (data?.topProviders ?? []).map((row) => ({ key: `${row.provider}:${row.biller}:${row.model}`, label: `${providerDisplayName(row.provider)} / ${row.model}`, sublabel: `Billed through ${providerDisplayName(row.biller)}`, costCents: row.costCents, inputTokens: row.inputTokens, cachedInputTokens: row.cachedInputTokens, outputTokens: row.outputTokens, })), [data?.topProviders], ); if (!selectedCompanyId) { return ; } if (isLoading) { return ; } if (error || !data) { return ; } const allTimeTokens = allTime ? totalTokens(allTime) : 0; const metaParts = [ data.user.membershipRole ?? "member", data.user.membershipStatus, `joined ${formatDate(data.user.joinedAt)}`, ]; return (
{data.user.image ? : null} {initials(displayName)}

{displayName}

@{data.user.slug}
{data.user.email ? {data.user.email} : null} {data.user.email ? · : null} {metaParts.join(" · ")}
{data.stats.map((entry) => )}

Recent tasks

{data.recentIssues.length}
{data.recentIssues.length === 0 ? (
No touched tasks yet.
) : (
    {data.recentIssues.map((issue) => (
  • {issue.identifier ?? issue.id.slice(0, 8)} {issue.title} {relativeTime(issue.updatedAt)}
  • ))}
)}

Recent activity

{data.recentActivity.length}
{data.recentActivity.length === 0 ? (
No direct user actions recorded yet.
) : (
    {data.recentActivity.map((event) => (
  • {event.action.replaceAll("_", " ")}
    {event.entityType} · {event.entityId.slice(0, 12)}
    {relativeTime(event.createdAt)}
  • ))}
)}
); }