import { data } from "react-router"; import type { Route } from "./+types/users.$username"; import { useTranslation } from "react-i18next"; import { ClientDate } from "~/components/ClientDate"; import { FollowButton } from "~/components/FollowButton"; import { SportBadge } from "~/components/SportBadge"; import { loadUserProfile } from "./users.$username.server"; import { federationEnabled, wantsActivityJson, handleFederationRequest, } from "~/lib/federation.server"; // Content negotiation: this URL is both the human profile (HTML) and the // ActivityPub actor IRI (application/activity+json). AP clients are // short-circuited to Fedify before the HTML loader runs; browsers fall // through untouched. export const middleware: Route.MiddlewareFunction[] = [ async ({ request }, next) => { if (federationEnabled() && wantsActivityJson(request)) { return handleFederationRequest(request); } return next(); }, ]; export async function loader({ params, request }: Route.LoaderArgs) { return data(await loadUserProfile(request, params.username)); } export function meta({ data: loaderData }: Route.MetaArgs) { const user = (loaderData as { user?: { username: string; displayName: string | null; domain: string; bio: string | null } })?.user; const displayName = user?.displayName ?? user?.username ?? "Profile"; const title = `${displayName} (@${user?.username}) — trails.cool`; const description = user?.bio && user.bio.length > 0 ? user.bio.slice(0, 280) : `${displayName} on trails.cool`; if (!user) return [{ title }]; return [ { title }, { property: "og:title", content: title }, { property: "og:description", content: description }, { property: "og:type", content: "profile" }, { property: "og:site_name", content: "trails.cool" }, { name: "twitter:card", content: "summary" }, { name: "twitter:title", content: title }, { name: "twitter:description", content: description }, ]; } export default function UserProfilePage({ loaderData }: Route.ComponentProps) { const { user, routes, activities, activitySort, isOwn, isDemoUser, followers, following, followState, isLoggedIn, canSeeContent } = loaderData; const { t } = useTranslation("journal"); return (
@{user.username}@{user.domain}
{user.bio &&{user.bio}
}{t("profile.privateStub.heading")}
{isLoggedIn ? t("profile.privateStub.bodyAuth") : t("profile.privateStub.bodyAnon")}
{!isLoggedIn && ( {t("auth.login")} )}{t("profile.noPublicRoutes")}
) : ( )}{t("profile.noPublicActivities")}
) : ({a.description}
)}