import { useState } from 'react'; import { data } from 'react-router'; import RouteErrorState from '../../../components/ui/error/RouteErrorState'; import { getOptionalAuth } from '../../auth/auth.server'; import { getPublicUserProfile, type PublicUserProfile } from '../users.server'; import ProfileAvatar from '../components/profile/ProfileAvatar'; import ProfileSidebarCard from '../components/profile/ProfileSidebarCard'; import ProfileTabs, { type ProfileTab } from '../components/profile/ProfileTabs'; import ActivityTab from '../components/profile/tabs/ActivityTab'; import FollowingTab from '../components/profile/tabs/FollowingTab'; import LikedTab from '../components/profile/tabs/LikedTab'; import { FILLER_ACTIVITY, FILLER_FOLLOWING, FILLER_LIKED, FILLER_PROFILE_META, } from '../utils/filler-user-profile'; import type { Route } from './+types/user-profile'; export const meta = ({ loaderData }: Route.MetaArgs) => [ { title: loaderData ? `${loaderData.profile.username} | The Biergarten App` : 'Profile | The Biergarten App', }, ]; export const loader = async ({ request, params }: Route.LoaderArgs) => { const auth = await getOptionalAuth(request); const profile = await getPublicUserProfile(params.id, auth?.accessToken); if (!profile) { throw data('User not found.', { status: 404, statusText: 'Not Found' }); } return { profile, isOwnProfile: auth?.userAccountId === profile.userAccountId, }; }; const joinedLabel = (isoDate: string) => `Joined ${new Intl.DateTimeFormat('en-US', { month: 'long', year: 'numeric' }).format(new Date(isoDate))}`; interface ProfileContentProps { profile: PublicUserProfile; isOwnProfile: boolean; } /** * Owns the profile-scoped interactive state. Keyed by `profile.userAccountId` in the * parent so React remounts it (resetting follow/tab state) when the route's `:id` changes, * since React Router reuses the route component itself across param-only navigations. */ const ProfileContent = ({ profile, isOwnProfile }: ProfileContentProps) => { const [activeTab, setActiveTab] = useState('activity'); const [isFollowingProfile, setIsFollowingProfile] = useState(false); const [following, setFollowing] = useState(() => FILLER_FOLLOWING.map((f) => ({ ...f, isFollowing: true })), ); const handleToggleFollowRow = (id: string) => { setFollowing((prev) => prev.map((f) => (f.id === id ? { ...f, isFollowing: !f.isFollowing } : f)), ); }; const displayName = `${profile.firstName} ${profile.lastName}`.trim() || profile.username; const stats = [ { label: 'Following', value: FILLER_PROFILE_META.following }, { label: 'Followers', value: FILLER_PROFILE_META.followers }, { label: 'Breweries visited', value: FILLER_PROFILE_META.breweriesVisited }, { label: 'Ratings given', value: FILLER_PROFILE_META.ratingsGiven }, ]; return (
setIsFollowingProfile((prev) => !prev)} />
, following: ( ), liked: , }} />
); }; const UserProfile = ({ loaderData }: Route.ComponentProps) => { const { profile, isOwnProfile } = loaderData; return ( ); }; export default UserProfile; export const ErrorBoundary = ({ error }: Route.ErrorBoundaryProps) => ( );