refactor page to use services
This commit is contained in:
@@ -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 && (
|
||||
|
||||
Reference in New Issue
Block a user