website cleanup
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
import Button from '@/components/ui/Button';
|
||||
import Input from '@/components/ui/Input';
|
||||
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
|
||||
import { useServices } from '@/lib/services/ServiceProvider';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
|
||||
@@ -13,6 +14,7 @@ interface CreateTeamFormProps {
|
||||
|
||||
export default function CreateTeamForm({ onCancel, onSuccess }: CreateTeamFormProps) {
|
||||
const router = useRouter();
|
||||
const { teamService } = useServices();
|
||||
const [formData, setFormData] = useState({
|
||||
name: '',
|
||||
tag: '',
|
||||
@@ -57,16 +59,13 @@ export default function CreateTeamForm({ onCancel, onSuccess }: CreateTeamFormPr
|
||||
setSubmitting(true);
|
||||
|
||||
try {
|
||||
const useCase = getCreateTeamUseCase();
|
||||
const result = await useCase.execute({
|
||||
const result = await teamService.createTeam({
|
||||
name: formData.name,
|
||||
tag: formData.tag.toUpperCase(),
|
||||
description: formData.description,
|
||||
ownerId: currentDriverId,
|
||||
leagues: [],
|
||||
});
|
||||
|
||||
const teamId = result.team.id;
|
||||
const teamId = result.id;
|
||||
|
||||
if (onSuccess) {
|
||||
onSuccess(teamId);
|
||||
@@ -169,4 +168,4 @@ export default function CreateTeamForm({ onCancel, onSuccess }: CreateTeamFormPr
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { TeamSummaryViewModel } from '@/lib/view-models/TeamSummaryViewMode
|
||||
import TeamCard from './TeamCard';
|
||||
|
||||
type SkillLevel = 'pro' | 'advanced' | 'intermediate' | 'beginner';
|
||||
type TeamSpecialization = 'endurance' | 'sprint' | 'mixed';
|
||||
|
||||
interface SkillLevelConfig {
|
||||
id: SkillLevel;
|
||||
@@ -35,6 +36,13 @@ export default function SkillLevelSection({
|
||||
|
||||
if (teams.length === 0) return null;
|
||||
|
||||
const specialization = (teamSpecialization: string | undefined): TeamSpecialization | undefined => {
|
||||
if (teamSpecialization === 'endurance' || teamSpecialization === 'sprint' || teamSpecialization === 'mixed') {
|
||||
return teamSpecialization;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mb-8">
|
||||
{/* Section Header */}
|
||||
@@ -81,12 +89,12 @@ export default function SkillLevelSection({
|
||||
name={team.name}
|
||||
description={team.description ?? ''}
|
||||
memberCount={team.memberCount}
|
||||
rating={team.rating}
|
||||
rating={null}
|
||||
totalWins={team.totalWins}
|
||||
totalRaces={team.totalRaces}
|
||||
performanceLevel={team.performanceLevel}
|
||||
performanceLevel={team.performanceLevel as SkillLevel}
|
||||
isRecruiting={team.isRecruiting}
|
||||
specialization={team.specialization}
|
||||
specialization={specialization(team.specialization)}
|
||||
region={team.region ?? ''}
|
||||
languages={team.languages}
|
||||
onClick={() => onTeamClick(team.id)}
|
||||
@@ -95,4 +103,4 @@ export default function SkillLevelSection({
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,21 +5,19 @@ import Card from '@/components/ui/Card';
|
||||
import Button from '@/components/ui/Button';
|
||||
import Input from '@/components/ui/Input';
|
||||
import { useServices } from '@/lib/services/ServiceProvider';
|
||||
import type { DriverDTO } from '@core/racing/application/dto/DriverDTO';
|
||||
import type { DriverDTO } from '@/lib/types/generated/DriverDTO';
|
||||
import type { TeamJoinRequestViewModel } from '@/lib/view-models/TeamJoinRequestViewModel';
|
||||
import type { TeamDetailsViewModel } from '@/lib/view-models/TeamDetailsViewModel';
|
||||
import type { UpdateTeamViewModel } from '@/lib/view-models/UpdateTeamViewModel';
|
||||
|
||||
interface TeamAdminProps {
|
||||
team: {
|
||||
id: string;
|
||||
name: string;
|
||||
tag: string;
|
||||
description: string;
|
||||
ownerId: string;
|
||||
};
|
||||
team: Pick<TeamDetailsViewModel, 'id' | 'name' | 'tag' | 'description' | 'ownerId'>;
|
||||
onUpdate: () => void;
|
||||
}
|
||||
|
||||
export default function TeamAdmin({ team, onUpdate }: TeamAdminProps) {
|
||||
const [joinRequests, setJoinRequests] = useState<TeamAdminJoinRequestViewModel[]>([]);
|
||||
const { teamJoinService, teamService } = useServices();
|
||||
const [joinRequests, setJoinRequests] = useState<TeamJoinRequestViewModel[]>([]);
|
||||
const [requestDrivers, setRequestDrivers] = useState<Record<string, DriverDTO>>({});
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
@@ -33,22 +31,13 @@ export default function TeamAdmin({ team, onUpdate }: TeamAdminProps) {
|
||||
const load = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const viewModel = await loadTeamAdminViewModel({
|
||||
id: team.id,
|
||||
name: team.name,
|
||||
tag: team.tag,
|
||||
description: team.description,
|
||||
ownerId: team.ownerId,
|
||||
});
|
||||
setJoinRequests(viewModel.requests);
|
||||
|
||||
const driversById: Record<string, DriverDTO> = {};
|
||||
for (const request of viewModel.requests) {
|
||||
if (request.driver) {
|
||||
driversById[request.driverId] = request.driver;
|
||||
}
|
||||
}
|
||||
setRequestDrivers(driversById);
|
||||
// Current build only supports read-only join requests. Driver hydration is
|
||||
// not provided by the API response, so we only display driverId.
|
||||
const currentUserId = team.ownerId;
|
||||
const isOwner = true;
|
||||
const requests = await teamJoinService.getJoinRequests(team.id, currentUserId, isOwner);
|
||||
setJoinRequests(requests);
|
||||
setRequestDrivers({});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -59,16 +48,8 @@ export default function TeamAdmin({ team, onUpdate }: TeamAdminProps) {
|
||||
|
||||
const handleApprove = async (requestId: string) => {
|
||||
try {
|
||||
const updated = await approveTeamJoinRequestAndReload(requestId, team.id);
|
||||
setJoinRequests(updated);
|
||||
const driversById: Record<string, DriverDTO> = {};
|
||||
for (const request of updated) {
|
||||
if (request.driver) {
|
||||
driversById[request.driverId] = request.driver;
|
||||
}
|
||||
}
|
||||
setRequestDrivers(driversById);
|
||||
onUpdate();
|
||||
void requestId;
|
||||
await teamJoinService.approveJoinRequest();
|
||||
} catch (error) {
|
||||
alert(error instanceof Error ? error.message : 'Failed to approve request');
|
||||
}
|
||||
@@ -76,15 +57,8 @@ export default function TeamAdmin({ team, onUpdate }: TeamAdminProps) {
|
||||
|
||||
const handleReject = async (requestId: string) => {
|
||||
try {
|
||||
const updated = await rejectTeamJoinRequestAndReload(requestId, team.id);
|
||||
setJoinRequests(updated);
|
||||
const driversById: Record<string, DriverDTO> = {};
|
||||
for (const request of updated) {
|
||||
if (request.driver) {
|
||||
driversById[request.driverId] = request.driver;
|
||||
}
|
||||
}
|
||||
setRequestDrivers(driversById);
|
||||
void requestId;
|
||||
await teamJoinService.rejectJoinRequest();
|
||||
} catch (error) {
|
||||
alert(error instanceof Error ? error.message : 'Failed to reject request');
|
||||
}
|
||||
@@ -92,13 +66,16 @@ export default function TeamAdmin({ team, onUpdate }: TeamAdminProps) {
|
||||
|
||||
const handleSaveChanges = async () => {
|
||||
try {
|
||||
await updateTeamDetails({
|
||||
teamId: team.id,
|
||||
const result: UpdateTeamViewModel = await teamService.updateTeam(team.id, {
|
||||
name: editedTeam.name,
|
||||
tag: editedTeam.tag,
|
||||
description: editedTeam.description,
|
||||
updatedByDriverId: team.ownerId,
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.successMessage);
|
||||
}
|
||||
|
||||
setEditMode(false);
|
||||
onUpdate();
|
||||
} catch (error) {
|
||||
@@ -201,40 +178,37 @@ export default function TeamAdmin({ team, onUpdate }: TeamAdminProps) {
|
||||
) : joinRequests.length > 0 ? (
|
||||
<div className="space-y-3">
|
||||
{joinRequests.map((request) => {
|
||||
const driver = requestDrivers[request.driverId] ?? request.driver;
|
||||
if (!driver) return null;
|
||||
const driver = requestDrivers[request.driverId] ?? null;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={request.id}
|
||||
key={request.requestId}
|
||||
className="flex items-center justify-between p-4 rounded-lg bg-deep-graphite border border-charcoal-outline"
|
||||
>
|
||||
<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)}
|
||||
{(driver?.name ?? request.driverId).charAt(0)}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h4 className="text-white font-medium">{driver.name}</h4>
|
||||
<h4 className="text-white font-medium">{driver?.name ?? request.driverId}</h4>
|
||||
<p className="text-sm text-gray-400">
|
||||
{driver.country} • Requested {new Date(request.requestedAt).toLocaleDateString()}
|
||||
{driver?.country ?? 'Unknown'} • Requested {new Date(request.requestedAt).toLocaleDateString()}
|
||||
</p>
|
||||
{request.message && (
|
||||
<p className="text-sm text-gray-300 mt-1 italic">
|
||||
"{request.message}"
|
||||
</p>
|
||||
)}
|
||||
{/* Request message is not part of current API contract */}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => handleApprove(request.id)}
|
||||
onClick={() => handleApprove(request.requestId)}
|
||||
disabled
|
||||
>
|
||||
Approve
|
||||
</Button>
|
||||
<Button
|
||||
variant="danger"
|
||||
onClick={() => handleReject(request.id)}
|
||||
onClick={() => handleReject(request.requestId)}
|
||||
disabled
|
||||
>
|
||||
Reject
|
||||
</Button>
|
||||
@@ -266,4 +240,4 @@ export default function TeamAdmin({ team, onUpdate }: TeamAdminProps) {
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ import {
|
||||
Languages,
|
||||
} from 'lucide-react';
|
||||
|
||||
import { useServices } from '@/lib/services/ServiceProvider';
|
||||
|
||||
interface TeamCardProps {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -77,8 +79,8 @@ export default function TeamCard({
|
||||
languages,
|
||||
onClick,
|
||||
}: TeamCardProps) {
|
||||
const imageService = getImageService();
|
||||
const logoUrl = logo || imageService.getTeamLogo(id);
|
||||
const { mediaService } = useServices();
|
||||
const logoUrl = logo || mediaService.getTeamLogo(id);
|
||||
const performanceBadge = getPerformanceBadge(performanceLevel);
|
||||
const specializationBadge = getSpecializationBadge(specialization);
|
||||
|
||||
@@ -206,4 +208,4 @@ export default function TeamCard({
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useServices } from '@/lib/services/ServiceProvider';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import Image from 'next/image';
|
||||
|
||||
@@ -25,8 +26,8 @@ export default function TeamLadderRow({
|
||||
totalRaces,
|
||||
}: TeamLadderRowProps) {
|
||||
const router = useRouter();
|
||||
const imageService = getImageService();
|
||||
const logo = teamLogoUrl ?? imageService.getTeamLogo(teamId);
|
||||
const { mediaService } = useServices();
|
||||
const logo = teamLogoUrl ?? mediaService.getTeamLogo(teamId);
|
||||
|
||||
const handleClick = () => {
|
||||
router.push(`/teams/${teamId}`);
|
||||
@@ -74,4 +75,4 @@ export default function TeamLadderRow({
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ export default function TeamLeaderboardPreview({
|
||||
{/* Rating */}
|
||||
<div className="text-right">
|
||||
<p className="text-purple-400 font-mono font-semibold">
|
||||
{(team as any).rating?.toLocaleString() || '—'}
|
||||
{'—'}
|
||||
</p>
|
||||
<p className="text-xs text-gray-500">Rating</p>
|
||||
</div>
|
||||
@@ -172,4 +172,4 @@ export default function TeamLeaderboardPreview({
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,11 @@ import { useState, useEffect } from 'react';
|
||||
import Card from '@/components/ui/Card';
|
||||
import DriverIdentity from '@/components/drivers/DriverIdentity';
|
||||
import { useServices } from '@/lib/services/ServiceProvider';
|
||||
import type { TeamRole } from '@core/racing/domain/types/TeamMembership';
|
||||
import type { TeamMemberViewModel } from '@/lib/view-models/TeamMemberViewModel';
|
||||
|
||||
interface TeamMembershipSummary {
|
||||
driverId: string;
|
||||
role: TeamRole;
|
||||
joinedAt: Date;
|
||||
}
|
||||
type TeamRole = 'owner' | 'admin' | 'member';
|
||||
|
||||
type TeamMembershipSummary = Pick<TeamMemberViewModel, 'driverId' | 'role' | 'joinedAt'>;
|
||||
|
||||
interface TeamRosterProps {
|
||||
teamId: string;
|
||||
@@ -64,7 +62,7 @@ export default function TeamRoster({
|
||||
switch (role) {
|
||||
case 'owner':
|
||||
return 'bg-warning-amber/20 text-warning-amber';
|
||||
case 'manager':
|
||||
case 'admin':
|
||||
return 'bg-primary-blue/20 text-primary-blue';
|
||||
default:
|
||||
return 'bg-charcoal-outline text-gray-300';
|
||||
@@ -79,9 +77,9 @@ export default function TeamRoster({
|
||||
switch (role) {
|
||||
case 'owner':
|
||||
return 0;
|
||||
case 'manager':
|
||||
case 'admin':
|
||||
return 1;
|
||||
case 'driver':
|
||||
case 'member':
|
||||
return 2;
|
||||
default:
|
||||
return 3;
|
||||
@@ -192,8 +190,8 @@ export default function TeamRoster({
|
||||
onChangeRole?.(driver.id, e.target.value as TeamRole)
|
||||
}
|
||||
>
|
||||
<option value="driver">Driver</option>
|
||||
<option value="manager">Manager</option>
|
||||
<option value="member">Member</option>
|
||||
<option value="admin">Admin</option>
|
||||
</select>
|
||||
|
||||
<button
|
||||
@@ -214,4 +212,4 @@ export default function TeamRoster({
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ export default function TopThreePodium({ teams, onClick }: TopThreePodiumProps)
|
||||
<button
|
||||
key={team.id}
|
||||
type="button"
|
||||
onClick={() => onTeamClick(team.id)}
|
||||
onClick={() => onClick(team.id)}
|
||||
className="flex flex-col items-center group"
|
||||
>
|
||||
{/* Team card */}
|
||||
@@ -142,7 +142,7 @@ export default function TopThreePodium({ teams, onClick }: TopThreePodiumProps)
|
||||
|
||||
{/* Rating */}
|
||||
<p className={`text-lg md:text-xl font-mono font-bold ${getPositionColor(position)} text-center`}>
|
||||
{(team as any).rating?.toLocaleString() || '—'}
|
||||
{'—'}
|
||||
</p>
|
||||
|
||||
{/* Stats row */}
|
||||
@@ -172,4 +172,4 @@ export default function TopThreePodium({ teams, onClick }: TopThreePodiumProps)
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user