wip
This commit is contained in:
@@ -4,7 +4,8 @@ import { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import Button from '@/components/ui/Button';
|
||||
import Input from '@/components/ui/Input';
|
||||
import { createTeam, getCurrentDriverId } from '@/lib/racingLegacyFacade';
|
||||
import { getCreateTeamUseCase } from '@/lib/di-container';
|
||||
import { useEffectiveDriverId } from '@/lib/currentDriver';
|
||||
|
||||
interface CreateTeamFormProps {
|
||||
onCancel?: () => void;
|
||||
@@ -20,6 +21,7 @@ export default function CreateTeamForm({ onCancel, onSuccess }: CreateTeamFormPr
|
||||
});
|
||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const currentDriverId = useEffectiveDriverId();
|
||||
|
||||
const validateForm = () => {
|
||||
const newErrors: Record<string, string> = {};
|
||||
@@ -56,18 +58,21 @@ export default function CreateTeamForm({ onCancel, onSuccess }: CreateTeamFormPr
|
||||
setSubmitting(true);
|
||||
|
||||
try {
|
||||
getCurrentDriverId(); // ensure identity initialized
|
||||
const team = createTeam({
|
||||
const useCase = getCreateTeamUseCase();
|
||||
const result = await useCase.execute({
|
||||
name: formData.name,
|
||||
tag: formData.tag.toUpperCase(),
|
||||
description: formData.description,
|
||||
ownerId: currentDriverId,
|
||||
leagues: [],
|
||||
});
|
||||
|
||||
const teamId = result.team.id;
|
||||
|
||||
if (onSuccess) {
|
||||
onSuccess(team.id);
|
||||
onSuccess(teamId);
|
||||
} else {
|
||||
router.push(`/teams/${team.id}`);
|
||||
router.push(`/teams/${teamId}`);
|
||||
}
|
||||
} catch (error) {
|
||||
alert(error instanceof Error ? error.message : 'Failed to create team');
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import Button from '@/components/ui/Button';
|
||||
import {
|
||||
getCurrentDriverId,
|
||||
getTeamMembership,
|
||||
getDriverTeam,
|
||||
joinTeam,
|
||||
requestToJoinTeam,
|
||||
leaveTeam,
|
||||
} from '@/lib/racingLegacyFacade';
|
||||
getJoinTeamUseCase,
|
||||
getLeaveTeamUseCase,
|
||||
getGetDriverTeamQuery,
|
||||
getTeamMembershipRepository,
|
||||
} from '@/lib/di-container';
|
||||
import { useEffectiveDriverId } from '@/lib/currentDriver';
|
||||
import type { TeamMembership } from '@gridpilot/racing';
|
||||
|
||||
interface JoinTeamButtonProps {
|
||||
teamId: string;
|
||||
@@ -23,18 +23,50 @@ export default function JoinTeamButton({
|
||||
onUpdate,
|
||||
}: JoinTeamButtonProps) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const currentDriverId = getCurrentDriverId();
|
||||
const membership = getTeamMembership(teamId, currentDriverId);
|
||||
const currentTeam = getDriverTeam(currentDriverId);
|
||||
const currentDriverId = useEffectiveDriverId();
|
||||
const [membership, setMembership] = useState<TeamMembership | null>(null);
|
||||
const [currentTeamName, setCurrentTeamName] = useState<string | null>(null);
|
||||
const [currentTeamId, setCurrentTeamId] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
const membershipRepo = getTeamMembershipRepository();
|
||||
const m = await membershipRepo.getMembership(teamId, currentDriverId);
|
||||
setMembership(m);
|
||||
|
||||
const driverTeamQuery = getGetDriverTeamQuery();
|
||||
const driverTeam = await driverTeamQuery.execute({ driverId: currentDriverId });
|
||||
if (driverTeam) {
|
||||
setCurrentTeamId(driverTeam.team.id);
|
||||
setCurrentTeamName(driverTeam.team.name);
|
||||
} else {
|
||||
setCurrentTeamId(null);
|
||||
setCurrentTeamName(null);
|
||||
}
|
||||
};
|
||||
void load();
|
||||
}, [teamId, currentDriverId]);
|
||||
|
||||
const handleJoin = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
if (requiresApproval) {
|
||||
requestToJoinTeam(teamId, currentDriverId);
|
||||
const membershipRepo = getTeamMembershipRepository();
|
||||
const existing = await membershipRepo.getMembership(teamId, currentDriverId);
|
||||
if (existing) {
|
||||
throw new Error('Already a member or have a pending request');
|
||||
}
|
||||
|
||||
await membershipRepo.saveJoinRequest({
|
||||
id: `team-request-${Date.now()}`,
|
||||
teamId,
|
||||
driverId: currentDriverId,
|
||||
requestedAt: new Date(),
|
||||
});
|
||||
alert('Join request sent! Wait for team approval.');
|
||||
} else {
|
||||
joinTeam(teamId, currentDriverId);
|
||||
const useCase = getJoinTeamUseCase();
|
||||
await useCase.execute({ teamId, driverId: currentDriverId });
|
||||
alert('Successfully joined team!');
|
||||
}
|
||||
onUpdate?.();
|
||||
@@ -52,7 +84,8 @@ export default function JoinTeamButton({
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
leaveTeam(teamId, currentDriverId);
|
||||
const useCase = getLeaveTeamUseCase();
|
||||
await useCase.execute({ teamId, driverId: currentDriverId });
|
||||
alert('Successfully left team');
|
||||
onUpdate?.();
|
||||
} catch (error) {
|
||||
@@ -84,10 +117,10 @@ export default function JoinTeamButton({
|
||||
}
|
||||
|
||||
// Already on another team
|
||||
if (currentTeam && currentTeam.team.id !== teamId) {
|
||||
if (currentTeamId && currentTeamId !== teamId) {
|
||||
return (
|
||||
<Button variant="secondary" disabled>
|
||||
Already on {currentTeam.team.name}
|
||||
Already on {currentTeamName}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,17 +4,16 @@ import { useState, useEffect } from 'react';
|
||||
import Card from '@/components/ui/Card';
|
||||
import Button from '@/components/ui/Button';
|
||||
import Input from '@/components/ui/Input';
|
||||
import { getDriverRepository } from '@/lib/di-container';
|
||||
import {
|
||||
getDriverRepository,
|
||||
getGetTeamJoinRequestsQuery,
|
||||
getApproveTeamJoinRequestUseCase,
|
||||
getRejectTeamJoinRequestUseCase,
|
||||
getUpdateTeamUseCase,
|
||||
} from '@/lib/di-container';
|
||||
import { EntityMappers } from '@gridpilot/racing/application/mappers/EntityMappers';
|
||||
import type { DriverDTO } from '@gridpilot/racing/application/dto/DriverDTO';
|
||||
import {
|
||||
Team,
|
||||
TeamJoinRequest,
|
||||
getTeamJoinRequests,
|
||||
approveTeamJoinRequest,
|
||||
rejectTeamJoinRequest,
|
||||
updateTeam,
|
||||
} from '@/lib/racingLegacyFacade';
|
||||
import type { Team, TeamJoinRequest } from '@gridpilot/racing';
|
||||
|
||||
interface TeamAdminProps {
|
||||
team: Team;
|
||||
@@ -33,11 +32,12 @@ export default function TeamAdmin({ team, onUpdate }: TeamAdminProps) {
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
loadJoinRequests();
|
||||
void loadJoinRequests();
|
||||
}, [team.id]);
|
||||
|
||||
const loadJoinRequests = async () => {
|
||||
const requests = getTeamJoinRequests(team.id);
|
||||
const query = getGetTeamJoinRequestsQuery();
|
||||
const requests = await query.execute({ teamId: team.id });
|
||||
setJoinRequests(requests);
|
||||
|
||||
const driverRepo = getDriverRepository();
|
||||
@@ -60,7 +60,8 @@ export default function TeamAdmin({ team, onUpdate }: TeamAdminProps) {
|
||||
|
||||
const handleApprove = async (requestId: string) => {
|
||||
try {
|
||||
approveTeamJoinRequest(requestId);
|
||||
const useCase = getApproveTeamJoinRequestUseCase();
|
||||
await useCase.execute({ requestId });
|
||||
await loadJoinRequests();
|
||||
onUpdate();
|
||||
} catch (error) {
|
||||
@@ -70,16 +71,26 @@ export default function TeamAdmin({ team, onUpdate }: TeamAdminProps) {
|
||||
|
||||
const handleReject = async (requestId: string) => {
|
||||
try {
|
||||
rejectTeamJoinRequest(requestId);
|
||||
const useCase = getRejectTeamJoinRequestUseCase();
|
||||
await useCase.execute({ requestId });
|
||||
await loadJoinRequests();
|
||||
} catch (error) {
|
||||
alert(error instanceof Error ? error.message : 'Failed to reject request');
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveChanges = () => {
|
||||
const handleSaveChanges = async () => {
|
||||
try {
|
||||
updateTeam(team.id, editedTeam, team.ownerId);
|
||||
const useCase = getUpdateTeamUseCase();
|
||||
await useCase.execute({
|
||||
teamId: team.id,
|
||||
updates: {
|
||||
name: editedTeam.name,
|
||||
tag: editedTeam.tag,
|
||||
description: editedTeam.description,
|
||||
},
|
||||
updatedBy: team.ownerId,
|
||||
});
|
||||
setEditMode(false);
|
||||
onUpdate();
|
||||
} catch (error) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import Image from 'next/image';
|
||||
import Card from '../ui/Card';
|
||||
import { getTeamLogoUrl } from '@/lib/racingLegacyFacade';
|
||||
import { getImageService } from '@/lib/di-container';
|
||||
|
||||
interface TeamCardProps {
|
||||
id: string;
|
||||
@@ -10,6 +10,9 @@ interface TeamCardProps {
|
||||
logo?: string;
|
||||
memberCount: number;
|
||||
leagues: string[];
|
||||
rating?: number | null;
|
||||
totalWins?: number;
|
||||
totalRaces?: number;
|
||||
performanceLevel?: 'beginner' | 'intermediate' | 'advanced' | 'pro';
|
||||
onClick?: () => void;
|
||||
}
|
||||
@@ -20,6 +23,9 @@ export default function TeamCard({
|
||||
logo,
|
||||
memberCount,
|
||||
leagues,
|
||||
rating,
|
||||
totalWins,
|
||||
totalRaces,
|
||||
performanceLevel,
|
||||
onClick,
|
||||
}: TeamCardProps) {
|
||||
@@ -40,7 +46,7 @@ export default function TeamCard({
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="w-16 h-16 bg-charcoal-outline rounded-lg flex items-center justify-center flex-shrink-0 overflow-hidden">
|
||||
<Image
|
||||
src={logo || getTeamLogoUrl(id)}
|
||||
src={logo || getImageService().getTeamLogo(id)}
|
||||
alt={name}
|
||||
width={64}
|
||||
height={64}
|
||||
@@ -54,6 +60,11 @@ export default function TeamCard({
|
||||
<p className="text-sm text-gray-400">
|
||||
{memberCount} {memberCount === 1 ? 'member' : 'members'}
|
||||
</p>
|
||||
{typeof rating === 'number' && (
|
||||
<p className="text-xs text-primary-blue mt-1">
|
||||
Team rating: <span className="font-semibold">{Math.round(rating)}</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -69,6 +80,27 @@ export default function TeamCard({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-3 gap-4 text-center">
|
||||
<div>
|
||||
<div className="text-sm text-gray-400">Rating</div>
|
||||
<div className="text-lg font-semibold text-primary-blue">
|
||||
{typeof rating === 'number' ? Math.round(rating) : '—'}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm text-gray-400">Wins</div>
|
||||
<div className="text-lg font-semibold text-green-400">
|
||||
{totalWins ?? 0}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm text-gray-400">Races</div>
|
||||
<div className="text-lg font-semibold text-white">
|
||||
{totalRaces ?? 0}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium text-gray-400">Active in:</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
|
||||
78
apps/website/components/teams/TeamLadderRow.tsx
Normal file
78
apps/website/components/teams/TeamLadderRow.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
'use client';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
import Image from 'next/image';
|
||||
import { getImageService } from '@/lib/di-container';
|
||||
|
||||
export interface TeamLadderRowProps {
|
||||
rank: number;
|
||||
teamId: string;
|
||||
teamName: string;
|
||||
teamLogoUrl?: string;
|
||||
memberCount: number;
|
||||
teamRating: number | null;
|
||||
totalWins: number;
|
||||
totalRaces: number;
|
||||
}
|
||||
|
||||
export default function TeamLadderRow({
|
||||
rank,
|
||||
teamId,
|
||||
teamName,
|
||||
teamLogoUrl,
|
||||
memberCount,
|
||||
teamRating,
|
||||
totalWins,
|
||||
totalRaces,
|
||||
}: TeamLadderRowProps) {
|
||||
const router = useRouter();
|
||||
const imageService = getImageService();
|
||||
const logo = teamLogoUrl ?? imageService.getTeamLogo(teamId);
|
||||
|
||||
const handleClick = () => {
|
||||
router.push(`/teams/${teamId}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<tr
|
||||
onClick={handleClick}
|
||||
className="cursor-pointer border-b border-charcoal-outline/60 hover:bg-iron-gray/30 transition-colors"
|
||||
>
|
||||
<td className="py-3 px-4 text-sm text-gray-300 font-semibold">#{rank}</td>
|
||||
<td className="py-3 px-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-md overflow-hidden bg-charcoal-outline flex-shrink-0">
|
||||
<Image
|
||||
src={logo}
|
||||
alt={teamName}
|
||||
width={32}
|
||||
height={32}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-sm font-semibold text-white truncate">
|
||||
{teamName}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-3 px-4 text-sm">
|
||||
<span className="text-primary-blue font-semibold">
|
||||
{teamRating !== null ? Math.round(teamRating) : '—'}
|
||||
</span>
|
||||
</td>
|
||||
<td className="py-3 px-4 text-sm">
|
||||
<span className="text-green-400 font-semibold">{totalWins}</span>
|
||||
</td>
|
||||
<td className="py-3 px-4 text-sm">
|
||||
<span className="text-white">{totalRaces}</span>
|
||||
</td>
|
||||
<td className="py-3 px-4 text-sm">
|
||||
<span className="text-gray-300">
|
||||
{memberCount} {memberCount === 1 ? 'member' : 'members'}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
@@ -2,10 +2,11 @@
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import Card from '@/components/ui/Card';
|
||||
import DriverIdentity from '@/components/drivers/DriverIdentity';
|
||||
import { getDriverRepository, getDriverStats } from '@/lib/di-container';
|
||||
import { EntityMappers } from '@gridpilot/racing/application/mappers/EntityMappers';
|
||||
import type { DriverDTO } from '@gridpilot/racing/application/dto/DriverDTO';
|
||||
import { TeamMembership, TeamRole } from '@/lib/racingLegacyFacade';
|
||||
import type { TeamMembership, TeamRole } from '@gridpilot/racing';
|
||||
|
||||
interface TeamRosterProps {
|
||||
teamId: string;
|
||||
@@ -33,7 +34,7 @@ export default function TeamRoster({
|
||||
const driverMap: Record<string, DriverDTO> = {};
|
||||
|
||||
for (const membership of memberships) {
|
||||
const driver = allDrivers.find(d => d.id === membership.driverId);
|
||||
const driver = allDrivers.find((d) => d.id === membership.driverId);
|
||||
if (driver) {
|
||||
const dto = EntityMappers.toDriverDTO(driver);
|
||||
if (dto) {
|
||||
@@ -41,12 +42,12 @@ export default function TeamRoster({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
setDrivers(driverMap);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
loadDrivers();
|
||||
|
||||
void loadDrivers();
|
||||
}, [memberships]);
|
||||
|
||||
const getRoleBadgeColor = (role: TeamRole) => {
|
||||
@@ -85,14 +86,15 @@ export default function TeamRoster({
|
||||
}
|
||||
});
|
||||
|
||||
const teamAverageRating = memberships.length > 0
|
||||
? Math.round(
|
||||
memberships.reduce((sum, m) => {
|
||||
const stats = getDriverStats(m.driverId);
|
||||
return sum + (stats?.rating || 0);
|
||||
}, 0) / memberships.length
|
||||
)
|
||||
: 0;
|
||||
const teamAverageRating =
|
||||
memberships.length > 0
|
||||
? Math.round(
|
||||
memberships.reduce((sum, m) => {
|
||||
const stats = getDriverStats(m.driverId);
|
||||
return sum + (stats?.rating || 0);
|
||||
}, 0) / memberships.length,
|
||||
)
|
||||
: 0;
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
@@ -108,10 +110,11 @@ export default function TeamRoster({
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold text-white">Team Roster</h3>
|
||||
<p className="text-sm text-gray-400 mt-1">
|
||||
{memberships.length} {memberships.length === 1 ? 'member' : 'members'} • Avg Rating: <span className="text-primary-blue font-medium">{teamAverageRating}</span>
|
||||
{memberships.length} {memberships.length === 1 ? 'member' : 'members'} • Avg Rating:{' '}
|
||||
<span className="text-primary-blue font-medium">{teamAverageRating}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<label className="text-sm text-gray-400">Sort by:</label>
|
||||
<select
|
||||
@@ -125,70 +128,67 @@ export default function TeamRoster({
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="space-y-3">
|
||||
{sortedMemberships.map((membership) => {
|
||||
const driver = drivers[membership.driverId];
|
||||
const driverStats = getDriverStats(membership.driverId);
|
||||
if (!driver) return null;
|
||||
|
||||
const canManageMembership = isAdmin && membership.role !== 'owner';
|
||||
|
||||
return (
|
||||
<div
|
||||
key={membership.driverId}
|
||||
className="flex items-center justify-between p-4 rounded-lg bg-deep-graphite border border-charcoal-outline hover:border-charcoal-outline/60 transition-colors"
|
||||
>
|
||||
<div className="flex items-center gap-4 flex-1">
|
||||
<div className="w-12 h-12 rounded-full bg-primary-blue/20 flex items-center justify-center text-lg font-bold text-white">
|
||||
{driver.name.charAt(0)}
|
||||
</div>
|
||||
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<h4 className="text-white font-medium">{driver.name}</h4>
|
||||
<span className={`px-2 py-1 rounded text-xs font-medium ${getRoleBadgeColor(membership.role)}`}>
|
||||
{getRoleLabel(membership.role)}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-gray-400">
|
||||
{driver.country} • Joined {new Date(membership.joinedAt).toLocaleDateString()}
|
||||
</p>
|
||||
</div>
|
||||
<DriverIdentity
|
||||
driver={driver}
|
||||
href={`/drivers/${driver.id}?from=team&teamId=${teamId}`}
|
||||
contextLabel={getRoleLabel(membership.role)}
|
||||
meta={
|
||||
<span>
|
||||
{driver.country} • Joined{' '}
|
||||
{new Date(membership.joinedAt).toLocaleDateString()}
|
||||
</span>
|
||||
}
|
||||
size="md"
|
||||
/>
|
||||
|
||||
{driverStats && (
|
||||
<div className="flex items-center gap-6 text-center">
|
||||
<div>
|
||||
<div className="text-lg font-bold text-primary-blue">{driverStats.rating}</div>
|
||||
<div className="text-xs text-gray-400">Rating</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm text-gray-300">#{driverStats.overallRank}</div>
|
||||
<div className="text-xs text-gray-500">Rank</div>
|
||||
{driverStats && (
|
||||
<div className="flex items-center gap-6 text-center">
|
||||
<div>
|
||||
<div className="text-lg font-bold text-primary-blue">
|
||||
{driverStats.rating}
|
||||
</div>
|
||||
<div className="text-xs text-gray-400">Rating</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm text-gray-300">#{driverStats.overallRank}</div>
|
||||
<div className="text-xs text-gray-500">Rank</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isAdmin && membership.role !== 'owner' && (
|
||||
{canManageMembership && (
|
||||
<div className="flex items-center gap-2">
|
||||
{onChangeRole && (
|
||||
<select
|
||||
className="px-3 py-2 bg-iron-gray border-0 rounded text-white ring-1 ring-inset ring-charcoal-outline focus:ring-2 focus:ring-primary-blue transition-all duration-150 text-sm"
|
||||
value={membership.role}
|
||||
onChange={(e) => onChangeRole(membership.driverId, e.target.value as TeamRole)}
|
||||
>
|
||||
<option value="driver">Driver</option>
|
||||
<option value="manager">Manager</option>
|
||||
</select>
|
||||
)}
|
||||
|
||||
{onRemoveMember && (
|
||||
<button
|
||||
onClick={() => onRemoveMember(membership.driverId)}
|
||||
className="px-3 py-2 bg-danger-red/20 hover:bg-danger-red/30 text-danger-red rounded text-sm font-medium transition-colors"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
)}
|
||||
<select
|
||||
className="px-3 py-2 bg-iron-gray border-0 rounded text-white ring-1 ring-inset ring-charcoal-outline focus:ring-2 focus:ring-primary-blue transition-all duration-150 text-sm"
|
||||
value={membership.role}
|
||||
onChange={(e) =>
|
||||
onChangeRole?.(membership.driverId, e.target.value as TeamRole)
|
||||
}
|
||||
>
|
||||
<option value="driver">Driver</option>
|
||||
<option value="manager">Manager</option>
|
||||
</select>
|
||||
|
||||
<button
|
||||
onClick={() => onRemoveMember?.(membership.driverId)}
|
||||
className="px-3 py-2 bg-danger-red/20 hover:bg-danger-red/30 text-danger-red rounded text-sm font-medium transition-colors"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -197,9 +197,7 @@ export default function TeamRoster({
|
||||
</div>
|
||||
|
||||
{memberships.length === 0 && (
|
||||
<div className="text-center py-8 text-gray-400">
|
||||
No team members yet.
|
||||
</div>
|
||||
<div className="text-center py-8 text-gray-400">No team members yet.</div>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import Card from '@/components/ui/Card';
|
||||
import { getStandingRepository, getLeagueRepository } from '@/lib/di-container';
|
||||
import { getStandingRepository, getLeagueRepository, getTeamMembershipRepository } from '@/lib/di-container';
|
||||
import { EntityMappers } from '@gridpilot/racing/application/mappers/EntityMappers';
|
||||
import type { LeagueDTO } from '@gridpilot/racing/application/dto/LeagueDTO';
|
||||
import { getTeamMembers } from '@/lib/racingLegacyFacade';
|
||||
|
||||
interface TeamStandingsProps {
|
||||
teamId: string;
|
||||
@@ -29,7 +28,8 @@ export default function TeamStandings({ teamId, leagues }: TeamStandingsProps) {
|
||||
const loadStandings = async () => {
|
||||
const standingRepo = getStandingRepository();
|
||||
const leagueRepo = getLeagueRepository();
|
||||
const members = getTeamMembers(teamId);
|
||||
const teamMembershipRepo = getTeamMembershipRepository();
|
||||
const members = await teamMembershipRepo.getTeamMembers(teamId);
|
||||
const memberIds = members.map(m => m.driverId);
|
||||
|
||||
const teamStandings: TeamLeagueStanding[] = [];
|
||||
|
||||
Reference in New Issue
Block a user