page wrapper
This commit is contained in:
@@ -17,8 +17,10 @@ import type {
|
||||
} from '@/lib/view-models/DriverProfileViewModel';
|
||||
import { getMediaUrl } from '@/lib/utilities/media';
|
||||
|
||||
// Shared state components
|
||||
import { StateContainer } from '@/components/shared/state/StateContainer';
|
||||
// New architecture components
|
||||
import { StatefulPageWrapper } from '@/components/shared/state/StatefulPageWrapper';
|
||||
|
||||
// Icons
|
||||
import {
|
||||
Activity,
|
||||
Award,
|
||||
@@ -255,30 +257,60 @@ function FinishDistributionChart({ wins, podiums, topTen, total }: FinishDistrib
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MAIN PAGE COMPONENT
|
||||
// TEMPLATE COMPONENT
|
||||
// ============================================================================
|
||||
|
||||
export default function ProfilePage() {
|
||||
interface ProfileTemplateProps {
|
||||
data: DriverProfileViewModel;
|
||||
onEdit: () => void;
|
||||
onAddFriend: () => void;
|
||||
activeTab: ProfileTab;
|
||||
setActiveTab: (tab: ProfileTab) => void;
|
||||
friendRequestSent: boolean;
|
||||
isOwnProfile: boolean;
|
||||
handleSaveSettings: (updates: { bio?: string; country?: string }) => Promise<void>;
|
||||
editMode: boolean;
|
||||
setEditMode: (edit: boolean) => void;
|
||||
}
|
||||
|
||||
function ProfileTemplate({
|
||||
data,
|
||||
onEdit,
|
||||
onAddFriend,
|
||||
activeTab,
|
||||
setActiveTab,
|
||||
friendRequestSent,
|
||||
isOwnProfile,
|
||||
handleSaveSettings,
|
||||
editMode,
|
||||
setEditMode
|
||||
}: ProfileTemplateProps) {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const tabParam = searchParams.get('tab') as ProfileTab | null;
|
||||
|
||||
const driverService = useInject(DRIVER_SERVICE_TOKEN);
|
||||
const mediaService = useInject(MEDIA_SERVICE_TOKEN);
|
||||
// Extract data from ViewModel
|
||||
const currentDriver = data.currentDriver;
|
||||
if (!currentDriver) {
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto px-4">
|
||||
<Card className="text-center py-12">
|
||||
<User className="w-16 h-16 text-gray-600 mx-auto mb-4" />
|
||||
<p className="text-gray-400 mb-2">No driver profile found</p>
|
||||
<p className="text-sm text-gray-500">Please create a driver profile to continue</p>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const effectiveDriverId = useEffectiveDriverId();
|
||||
const isOwnProfile = true; // This page is always your own profile
|
||||
|
||||
// Use React-Query hook for profile data
|
||||
const { data: profileData, isLoading: loading, error, retry } = useDriverProfile(effectiveDriverId || '');
|
||||
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState<ProfileTab>(tabParam || 'overview');
|
||||
const [friendRequestSent, setFriendRequestSent] = useState(false);
|
||||
const stats = data.stats;
|
||||
const teamMemberships = data.teamMemberships;
|
||||
const socialSummary = data.socialSummary;
|
||||
const extendedProfile = data.extendedProfile;
|
||||
const globalRank = currentDriver.globalRank || null;
|
||||
|
||||
// Update URL when tab changes
|
||||
useEffect(() => {
|
||||
if (tabParam !== activeTab) {
|
||||
if (searchParams.get('tab') !== activeTab) {
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
if (activeTab === 'overview') {
|
||||
params.delete('tab');
|
||||
@@ -288,62 +320,18 @@ export default function ProfilePage() {
|
||||
const query = params.toString();
|
||||
router.replace(`/profile${query ? `?${query}` : ''}`, { scroll: false });
|
||||
}
|
||||
}, [activeTab, tabParam, searchParams, router]);
|
||||
}, [activeTab, searchParams, router]);
|
||||
|
||||
// Sync tab from URL on mount and param change
|
||||
useEffect(() => {
|
||||
const tabParam = searchParams.get('tab') as ProfileTab | null;
|
||||
if (tabParam && tabParam !== activeTab) {
|
||||
setActiveTab(tabParam);
|
||||
}
|
||||
}, [tabParam]);
|
||||
|
||||
const handleSaveSettings = async (updates: { bio?: string; country?: string }) => {
|
||||
if (!profileData?.currentDriver) return;
|
||||
|
||||
try {
|
||||
const updatedProfile = await driverService.updateProfile(updates);
|
||||
// Update local state
|
||||
retry();
|
||||
setEditMode(false);
|
||||
} catch (error) {
|
||||
console.error('Failed to update profile:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddFriend = () => {
|
||||
setFriendRequestSent(true);
|
||||
// In production, this would call a use case
|
||||
};
|
||||
|
||||
// Show create form if no profile exists
|
||||
if (!loading && !profileData?.currentDriver && !error) {
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto px-4">
|
||||
<div className="text-center mb-8">
|
||||
<div className="flex h-16 w-16 items-center justify-center rounded-2xl bg-gradient-to-br from-primary-blue/20 to-purple-600/10 border border-primary-blue/30 mx-auto mb-4">
|
||||
<User className="w-8 h-8 text-primary-blue" />
|
||||
</div>
|
||||
<Heading level={1} className="mb-2">Create Your Driver Profile</Heading>
|
||||
<p className="text-gray-400">
|
||||
Join the GridPilot community and start your racing journey
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Card className="max-w-2xl mx-auto">
|
||||
<div className="mb-6">
|
||||
<h2 className="text-xl font-semibold text-white mb-2">Get Started</h2>
|
||||
<p className="text-gray-400 text-sm">
|
||||
Create your driver profile to join leagues, compete in races, and connect with other drivers.
|
||||
</p>
|
||||
</div>
|
||||
<CreateDriverForm />
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}, [searchParams]);
|
||||
|
||||
// Show edit mode
|
||||
if (editMode && profileData?.currentDriver) {
|
||||
if (editMode && currentDriver) {
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto px-4 space-y-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
@@ -352,55 +340,13 @@ export default function ProfilePage() {
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
<ProfileSettings driver={profileData.currentDriver} onSave={handleSaveSettings} />
|
||||
<ProfileSettings driver={currentDriver} onSave={handleSaveSettings} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<StateContainer
|
||||
data={profileData}
|
||||
isLoading={loading}
|
||||
error={error}
|
||||
retry={retry}
|
||||
config={{
|
||||
loading: { variant: 'full-screen', message: 'Loading profile...' },
|
||||
error: { variant: 'full-screen' },
|
||||
empty: {
|
||||
icon: User,
|
||||
title: 'No profile data',
|
||||
description: 'Unable to load your profile information',
|
||||
action: { label: 'Retry', onClick: retry }
|
||||
}
|
||||
}}
|
||||
>
|
||||
{(profileData) => {
|
||||
// Extract data from profileData ViewModel
|
||||
// At this point, we know profileData exists and currentDriver should exist
|
||||
// (otherwise we would have shown the create form above)
|
||||
const currentDriver = profileData.currentDriver;
|
||||
|
||||
// If currentDriver is null despite our checks, show empty state
|
||||
if (!currentDriver) {
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto px-4">
|
||||
<Card className="text-center py-12">
|
||||
<User className="w-16 h-16 text-gray-600 mx-auto mb-4" />
|
||||
<p className="text-gray-400 mb-2">No driver profile found</p>
|
||||
<p className="text-sm text-gray-500">Please create a driver profile to continue</p>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const stats = profileData.stats;
|
||||
const teamMemberships = profileData.teamMemberships;
|
||||
const socialSummary = profileData.socialSummary;
|
||||
const extendedProfile = profileData.extendedProfile;
|
||||
const globalRank = currentDriver.globalRank || null;
|
||||
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto px-4 pb-12 space-y-6">
|
||||
<div className="max-w-7xl mx-auto px-4 pb-12 space-y-6">
|
||||
{/* Hero Header Section */}
|
||||
<div className="relative rounded-2xl overflow-hidden bg-gradient-to-br from-iron-gray/80 via-iron-gray/60 to-deep-graphite border border-charcoal-outline">
|
||||
{/* Background Pattern */}
|
||||
@@ -497,7 +443,7 @@ export default function ProfilePage() {
|
||||
{isOwnProfile ? (
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => setEditMode(true)}
|
||||
onClick={onEdit}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Edit3 className="w-4 h-4" />
|
||||
@@ -507,7 +453,7 @@ export default function ProfilePage() {
|
||||
<>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={handleAddFriend}
|
||||
onClick={onAddFriend}
|
||||
disabled={friendRequestSent}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
@@ -1055,16 +1001,112 @@ export default function ProfilePage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'stats' && !stats && (
|
||||
<Card className="text-center py-12">
|
||||
<BarChart3 className="w-16 h-16 text-gray-600 mx-auto mb-4" />
|
||||
<p className="text-gray-400 mb-2">No statistics available yet</p>
|
||||
<p className="text-sm text-gray-500">Join a league and complete races to see detailed stats</p>
|
||||
</Card>
|
||||
)}
|
||||
{activeTab === 'stats' && !stats && (
|
||||
<Card className="text-center py-12">
|
||||
<BarChart3 className="w-16 h-16 text-gray-600 mx-auto mb-4" />
|
||||
<p className="text-gray-400 mb-2">No statistics available yet</p>
|
||||
<p className="text-sm text-gray-500">Join a league and complete races to see detailed stats</p>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MAIN PAGE COMPONENT
|
||||
// ============================================================================
|
||||
|
||||
export default function ProfilePage() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const tabParam = searchParams.get('tab') as ProfileTab | null;
|
||||
|
||||
const driverService = useInject(DRIVER_SERVICE_TOKEN);
|
||||
const mediaService = useInject(MEDIA_SERVICE_TOKEN);
|
||||
|
||||
const effectiveDriverId = useEffectiveDriverId();
|
||||
const isOwnProfile = true; // This page is always your own profile
|
||||
|
||||
// Use React-Query hook for profile data
|
||||
const { data: profileData, isLoading: loading, error, refetch } = useDriverProfile(effectiveDriverId || '');
|
||||
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState<ProfileTab>(tabParam || 'overview');
|
||||
const [friendRequestSent, setFriendRequestSent] = useState(false);
|
||||
|
||||
const handleSaveSettings = async (updates: { bio?: string; country?: string }) => {
|
||||
if (!profileData?.currentDriver) return;
|
||||
|
||||
try {
|
||||
const updatedProfile = await driverService.updateProfile(updates);
|
||||
// Update local state
|
||||
refetch();
|
||||
setEditMode(false);
|
||||
} catch (error) {
|
||||
console.error('Failed to update profile:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddFriend = () => {
|
||||
setFriendRequestSent(true);
|
||||
// In production, this would call a use case
|
||||
};
|
||||
|
||||
// Show create form if no profile exists
|
||||
if (!loading && !profileData?.currentDriver && !error) {
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto px-4">
|
||||
<div className="text-center mb-8">
|
||||
<div className="flex h-16 w-16 items-center justify-center rounded-2xl bg-gradient-to-br from-primary-blue/20 to-purple-600/10 border border-primary-blue/30 mx-auto mb-4">
|
||||
<User className="w-8 h-8 text-primary-blue" />
|
||||
</div>
|
||||
<Heading level={1} className="mb-2">Create Your Driver Profile</Heading>
|
||||
<p className="text-gray-400">
|
||||
Join the GridPilot community and start your racing journey
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</StateContainer>
|
||||
);
|
||||
|
||||
<Card className="max-w-2xl mx-auto">
|
||||
<div className="mb-6">
|
||||
<h2 className="text-xl font-semibold text-white mb-2">Get Started</h2>
|
||||
<p className="text-gray-400 text-sm">
|
||||
Create your driver profile to join leagues, compete in races, and connect with other drivers.
|
||||
</p>
|
||||
</div>
|
||||
<CreateDriverForm />
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<StatefulPageWrapper
|
||||
data={profileData}
|
||||
isLoading={loading}
|
||||
error={error}
|
||||
retry={refetch}
|
||||
Template={({ data }) => (
|
||||
<ProfileTemplate
|
||||
data={data}
|
||||
onEdit={() => setEditMode(true)}
|
||||
onAddFriend={handleAddFriend}
|
||||
activeTab={activeTab}
|
||||
setActiveTab={setActiveTab}
|
||||
friendRequestSent={friendRequestSent}
|
||||
isOwnProfile={isOwnProfile}
|
||||
handleSaveSettings={handleSaveSettings}
|
||||
editMode={editMode}
|
||||
setEditMode={setEditMode}
|
||||
/>
|
||||
)}
|
||||
loading={{ variant: 'full-screen', message: 'Loading profile...' }}
|
||||
errorConfig={{ variant: 'full-screen' }}
|
||||
empty={{
|
||||
icon: User,
|
||||
title: 'No profile data',
|
||||
description: 'Unable to load your profile information',
|
||||
action: { label: 'Retry', onClick: refetch }
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user