refactor page to use services

This commit is contained in:
2025-12-18 15:58:09 +01:00
parent f54fa5de5b
commit fc386db06a
45 changed files with 2254 additions and 1292 deletions

View File

@@ -6,15 +6,15 @@ import Button from '@/components/ui/Button';
import Card from '@/components/ui/Card';
import Image from 'next/image';
import { useParams } from 'next/navigation';
import { useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useState, useMemo } from 'react';
import JoinTeamButton from '@/components/teams/JoinTeamButton';
import TeamAdmin from '@/components/teams/TeamAdmin';
import TeamRoster from '@/components/teams/TeamRoster';
import TeamStandings from '@/components/teams/TeamStandings';
import { TeamDetailsPresenter } from '@/lib/presenters/TeamDetailsPresenter';
import { TeamMembersPresenter } from '@/lib/presenters/TeamMembersPresenter';
import type { TeamDetailsViewModel } from '@core/racing/application/presenters/ITeamDetailsPresenter';
import { ServiceFactory } from '@/lib/services/ServiceFactory';
import { TeamDetailsViewModel } from '@/lib/view-models/TeamDetailsViewModel';
import { TeamMemberViewModel } from '@/lib/view-models/TeamMemberViewModel';
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
@@ -29,60 +29,46 @@ interface TeamMembership {
type Tab = 'overview' | 'roster' | 'standings' | 'admin';
export default function TeamDetailPage() {
const params = useParams();
const teamId = params.id as string;
const params = useParams();
const teamId = params.id as string;
type TeamViewModel = TeamDetailsViewModel['team'];
const [team, setTeam] = useState<TeamDetailsViewModel | null>(null);
const [memberships, setMemberships] = useState<TeamMemberViewModel[]>([]);
const [activeTab, setActiveTab] = useState<Tab>('overview');
const [loading, setLoading] = useState(true);
const [isAdmin, setIsAdmin] = useState(false);
const currentDriverId = useEffectiveDriverId();
const isSponsorMode = useSponsorMode();
const [team, setTeam] = useState<TeamViewModel | null>(null);
const [memberships, setMemberships] = useState<TeamMembership[]>([]);
const [activeTab, setActiveTab] = useState<Tab>('overview');
const [loading, setLoading] = useState(true);
const [isAdmin, setIsAdmin] = useState(false);
const currentDriverId = useEffectiveDriverId();
const isSponsorMode = useSponsorMode();
// Initialize services
const serviceFactory = useMemo(() => new ServiceFactory(process.env.NEXT_PUBLIC_API_BASE_URL || ''), []);
const teamService = useMemo(() => serviceFactory.createTeamService(), [serviceFactory]);
const mediaService = useMemo(() => serviceFactory.createMediaService(), [serviceFactory]);
const loadTeamData = useCallback(async () => {
setLoading(true);
try {
const detailsUseCase = getGetTeamDetailsUseCase();
const membersUseCase = getGetTeamMembersUseCase();
const teamDetails = await teamService.getTeamDetails(teamId, currentDriverId);
const detailsPresenter = new TeamDetailsPresenter();
await detailsUseCase.execute({ teamId, driverId: currentDriverId }, detailsPresenter);
const detailsViewModel = detailsPresenter.getViewModel();
if (!detailsViewModel) {
if (!teamDetails) {
setTeam(null);
setMemberships([]);
setIsAdmin(false);
return;
}
const teamMembersPresenter = new TeamMembersPresenter();
await membersUseCase.execute({ teamId }, teamMembersPresenter);
const membersViewModel = teamMembersPresenter.getViewModel();
const teamMembers = await teamService.getTeamMembers(teamId, currentDriverId, teamDetails.ownerId);
const teamMemberships: TeamMembership[] = (membersViewModel?.members ?? []).map((m) => ({
driverId: m.driverId,
role: m.role as TeamRole,
joinedAt: new Date(m.joinedAt),
}));
const adminStatus = teamDetails.isOwner ||
teamMembers.some(m => m.driverId === currentDriverId && (m.role === 'manager' || m.role === 'owner'));
const adminStatus =
teamMemberships.some(
(m: TeamMembership) =>
m.driverId === currentDriverId &&
(m.role === 'owner' || m.role === 'manager'),
) ?? false;
setTeam(detailsViewModel.team);
setMemberships(teamMemberships);
setTeam(teamDetails);
setMemberships(teamMembers);
setIsAdmin(adminStatus);
} finally {
setLoading(false);
}
}, [teamId, currentDriverId]);
}, [teamId, currentDriverId, teamService]);
useEffect(() => {
void loadTeamData();
@@ -98,13 +84,12 @@ export default function TeamDetailPage() {
}
try {
const membershipRepo = getTeamMembershipRepository();
const performer = await membershipRepo.getMembership(teamId, currentDriverId);
const performer = await teamService.getMembership(teamId, currentDriverId);
if (!performer || (performer.role !== 'owner' && performer.role !== 'manager')) {
throw new Error('Only owners or managers can remove members');
}
const membership = await membershipRepo.getMembership(teamId, driverId);
const membership = await teamService.getMembership(teamId, driverId);
if (!membership) {
throw new Error('Member not found');
}
@@ -112,7 +97,7 @@ export default function TeamDetailPage() {
throw new Error('Cannot remove the team owner');
}
await membershipRepo.removeMembership(teamId, driverId);
await teamService.removeMembership(teamId, driverId);
handleUpdate();
} catch (error) {
alert(error instanceof Error ? error.message : 'Failed to remove member');
@@ -121,13 +106,12 @@ export default function TeamDetailPage() {
const handleChangeRole = async (driverId: string, newRole: TeamRole) => {
try {
const membershipRepo = getTeamMembershipRepository();
const performer = await membershipRepo.getMembership(teamId, currentDriverId);
const performer = await teamService.getMembership(teamId, currentDriverId);
if (!performer || (performer.role !== 'owner' && performer.role !== 'manager')) {
throw new Error('Only owners or managers can update roles');
}
const membership = await membershipRepo.getMembership(teamId, driverId);
const membership = await teamService.getMembership(teamId, driverId);
if (!membership) {
throw new Error('Member not found');
}
@@ -135,10 +119,7 @@ export default function TeamDetailPage() {
throw new Error('Cannot change the owner role');
}
await membershipRepo.saveMembership({
...membership,
role: newRole,
});
await teamService.updateMembership(teamId, driverId, newRole);
handleUpdate();
} catch (error) {
alert(error instanceof Error ? error.message : 'Failed to change role');
@@ -184,7 +165,7 @@ export default function TeamDetailPage() {
const teamMetrics = [
MetricBuilders.members(memberships.length),
MetricBuilders.reach(memberships.length * 15),
MetricBuilders.races(team.leagues.length * 8),
MetricBuilders.races(0), // TODO: Get league count from team data
MetricBuilders.engagement(82),
];
@@ -218,7 +199,7 @@ export default function TeamDetailPage() {
<div className="flex items-start gap-6">
<div className="w-24 h-24 bg-charcoal-outline rounded-lg flex items-center justify-center flex-shrink-0 overflow-hidden">
<Image
src={getImageService().getTeamLogo(team.id)}
src={mediaService.getTeamLogo(team.id)}
alt={team.name}
width={96}
height={96}
@@ -229,23 +210,15 @@ export default function TeamDetailPage() {
<div>
<div className="flex items-center gap-3 mb-2">
<h1 className="text-3xl font-bold text-white">{team.name}</h1>
<span className="px-3 py-1 bg-charcoal-outline text-gray-300 rounded-full text-sm font-medium">
{team.tag}
</span>
{/* TODO: Add team tag when available */}
</div>
<p className="text-gray-300 mb-4 max-w-2xl">{team.description}</p>
<div className="flex items-center gap-4 text-sm text-gray-400">
<span>{memberships.length} {memberships.length === 1 ? 'member' : 'members'}</span>
<span></span>
<span>Created {new Date(team.createdAt).toLocaleDateString()}</span>
{team.leagues.length > 0 && (
<>
<span></span>
<span>{team.leagues.length} {team.leagues.length === 1 ? 'league' : 'leagues'}</span>
</>
)}
{/* TODO: Add created date when available */}
{/* TODO: Add league count when available */}
</div>
</div>
</div>
@@ -290,8 +263,8 @@ export default function TeamDetailPage() {
<h3 className="text-xl font-semibold text-white mb-4">Quick Stats</h3>
<div className="space-y-3">
<StatItem label="Members" value={memberships.length.toString()} color="text-primary-blue" />
<StatItem label="Leagues" value={team.leagues.length.toString()} color="text-green-400" />
<StatItem label="Founded" value={new Date(team.createdAt).toLocaleDateString('en-US', { month: 'short', year: 'numeric' })} color="text-gray-300" />
<StatItem label="Leagues" value="0" color="text-green-400" /> {/* TODO: Get league count */}
<StatItem label="Founded" value="Unknown" color="text-gray-300" /> {/* TODO: Get founded date */}
</div>
</Card>
</div>
@@ -316,7 +289,7 @@ export default function TeamDetailPage() {
)}
{activeTab === 'standings' && (
<TeamStandings teamId={teamId} leagues={team.leagues} />
<TeamStandings teamId={teamId} leagues={[]} />
)}
{activeTab === 'admin' && isAdmin && (