Files
gridpilot.gg/apps/website/templates/RosterAdminTemplate.tsx
2026-01-18 16:43:32 +01:00

168 lines
6.7 KiB
TypeScript

'use client';
import type { MembershipRole } from '@/lib/types/MembershipRole';
import type { LeagueRosterAdminViewData } from '@/lib/view-data/LeagueRosterAdminViewData';
import { Button } from '@/ui/Button';
import { Heading } from '@/ui/Heading';
import { Icon } from '@/ui/Icon';
import { Box } from '@/ui/primitives/Box';
import { Stack } from '@/ui/primitives/Stack';
import { Surface } from '@/ui/primitives/Surface';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/ui/Table';
import { Text } from '@/ui/Text';
import { Shield, UserMinus, UserPlus } from 'lucide-react';
import React from 'react';
interface RosterAdminTemplateProps {
viewData: LeagueRosterAdminViewData;
loading: boolean;
pendingCountLabel: string;
onApprove: (requestId: string) => Promise<void>;
onReject: (requestId: string) => Promise<void>;
onRoleChange: (driverId: string, newRole: MembershipRole) => Promise<void>;
onRemove: (driverId: string) => Promise<void>;
roleOptions: MembershipRole[];
}
export function RosterAdminTemplate({
viewData,
loading,
pendingCountLabel,
onApprove,
onReject,
onRoleChange,
onRemove,
roleOptions,
}: RosterAdminTemplateProps) {
const { joinRequests, members } = viewData;
return (
<Stack gap={8}>
{/* Join Requests Section */}
<Box>
<Stack direction="row" align="center" justify="between" mb={4}>
<Stack direction="row" align="center" gap={2}>
<Icon icon={UserPlus} size={4} color="text-primary-blue" />
<Heading level={5} color="text-primary-blue">PENDING JOIN REQUESTS</Heading>
</Stack>
<Box px={2} py={0.5} rounded="md" bg="bg-primary-blue/10" border borderColor="border-primary-blue/20">
<Text size="xs" color="text-primary-blue" weight="bold">{pendingCountLabel}</Text>
</Box>
</Stack>
{loading ? (
<Surface variant="dark" border rounded="lg" padding={12} center>
<Text color="text-gray-500">Loading requests...</Text>
</Surface>
) : joinRequests.length > 0 ? (
<Surface variant="dark" border rounded="lg" overflow="hidden">
<Stack gap={0}>
{joinRequests.map((req) => (
<Box key={req.id} p={4} borderBottom borderColor="border-charcoal-outline" hoverBg="bg-white/5" transition>
<Stack direction={{ base: 'col', md: 'row' }} align="center" justify="between" gap={4}>
<Stack gap={1}>
<Text weight="bold" color="text-white">{req.driver.name}</Text>
<Text size="xs" color="text-gray-500">{new Date(req.requestedAt).toLocaleString()}</Text>
{req.message && (
<Text size="sm" color="text-gray-400" mt={1}>&quot;{req.message}&quot;</Text>
)}
</Stack>
<Stack direction="row" gap={2}>
<Button onClick={() => onApprove(req.id)} variant="primary" size="sm">
Approve
</Button>
<Button onClick={() => onReject(req.id)} variant="secondary" size="sm">
Reject
</Button>
</Stack>
</Stack>
</Box>
))}
</Stack>
</Surface>
) : (
<Surface variant="dark" border rounded="lg" padding={8} center>
<Text color="text-gray-500">No pending join requests.</Text>
</Surface>
)}
</Box>
{/* Members Section */}
<Box>
<Stack direction="row" align="center" gap={2} mb={4}>
<Icon icon={Shield} size={4} color="text-performance-green" />
<Heading level={5} color="text-performance-green">ACTIVE ROSTER</Heading>
</Stack>
{loading ? (
<Surface variant="dark" border rounded="lg" padding={12} center>
<Text color="text-gray-500">Loading members...</Text>
</Surface>
) : members.length > 0 ? (
<Surface variant="dark" border rounded="lg" overflow="hidden">
<Table>
<TableHead>
<TableRow>
<TableHeader>Driver</TableHeader>
<TableHeader>Joined</TableHeader>
<TableHeader>Role</TableHeader>
<TableHeader textAlign="right">Actions</TableHeader>
</TableRow>
</TableHead>
<TableBody>
{members.map((member) => (
<TableRow key={member.driverId}>
<TableCell>
<Text weight="bold" color="text-white">{member.driver.name}</Text>
</TableCell>
<TableCell>
<Text size="sm" color="text-gray-400">{new Date(member.joinedAt).toLocaleDateString()}</Text>
</TableCell>
<TableCell>
<Box
as="select"
value={member.role}
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => onRoleChange(member.driverId, e.target.value as MembershipRole)}
bg="bg-iron-gray"
border
borderColor="border-charcoal-outline"
rounded="md"
px={2}
py={1}
fontSize="xs"
weight="bold"
color="text-white"
>
{roleOptions.map((role) => (
<Box as="option" key={role} value={role}>{role.toUpperCase()}</Box>
))}
</Box>
</TableCell>
<TableCell textAlign="right">
<Button
onClick={() => onRemove(member.driverId)}
variant="ghost"
size="sm"
>
<Stack direction="row" align="center" gap={1.5}>
<Icon icon={UserMinus} size={3.5} color="text-error-red" />
<Text size="xs" weight="bold" color="text-error-red">REMOVE</Text>
</Stack>
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Surface>
) : (
<Surface variant="dark" border rounded="lg" padding={8} center>
<Text color="text-gray-500">No members found.</Text>
</Surface>
)}
</Box>
</Stack>
);
}