Files
gridpilot.gg/apps/website/templates/RosterAdminTemplate.tsx
2026-01-15 18:52:03 +01:00

155 lines
5.3 KiB
TypeScript

'use client';
import React from 'react';
import { Card } from '@/ui/Card';
import { Text } from '@/ui/Text';
import { Button } from '@/ui/Button';
import { Select } from '@/ui/Select';
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
import { Heading } from '@/ui/Heading';
import { Surface } from '@/ui/Surface';
import type { MembershipRole } from '@/lib/types/MembershipRole';
import type { LeagueRosterAdminViewData } from '@/lib/view-data/LeagueRosterAdminViewData';
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={6}>
<Card>
<Stack gap={6}>
<Box>
<Heading level={1}>Roster Admin</Heading>
<Text size="sm" color="text-gray-400" block mt={1}>
Manage join requests and member roles.
</Text>
</Box>
<Box>
<Stack direction="row" align="center" justify="between" mb={4}>
<Heading level={2}>Pending join requests</Heading>
<Text size="xs" color="text-gray-500">
{pendingCountLabel}
</Text>
</Stack>
{loading ? (
<Text size="sm" color="text-gray-400">Loading</Text>
) : joinRequests.length > 0 ? (
<Stack gap={3}>
{joinRequests.map((req) => (
<Surface
key={req.id}
variant="muted"
rounded="lg"
border
padding={3}
>
<Stack direction="row" align="center" justify="between">
<Box>
<Text weight="medium" color="text-white" block>{req.driver.name}</Text>
<Text size="xs" color="text-gray-400" block mt={1}>{req.requestedAt}</Text>
{req.message && (
<Text size="xs" color="text-gray-500" block mt={1} truncate>{req.message}</Text>
)}
</Box>
<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>
</Surface>
))}
</Stack>
) : (
<Text size="sm" color="text-gray-500">No pending join requests.</Text>
)}
</Box>
<Box pt={6} borderTop borderColor="border-neutral-800">
<Box mb={4}>
<Heading level={2}>Members</Heading>
</Box>
{loading ? (
<Text size="sm" color="text-gray-400">Loading</Text>
) : members.length > 0 ? (
<Stack gap={3}>
{members.map((member) => (
<Surface
key={member.driverId}
variant="muted"
rounded="lg"
border
padding={3}
>
<Stack direction="row" align="center" justify="between" wrap gap={4}>
<Box>
<Text weight="medium" color="text-white" block>{member.driver.name}</Text>
<Text size="xs" color="text-gray-400" block mt={1}>{member.joinedAt}</Text>
</Box>
<Stack direction="row" align="center" gap={3}>
<Box>
<Select
value={member.role}
onChange={(e) => onRoleChange(member.driverId, e.target.value as MembershipRole)}
options={roleOptions.map((role) => ({ value: role, label: role }))}
/>
</Box>
<Button
onClick={() => onRemove(member.driverId)}
variant="secondary"
size="sm"
>
Remove
</Button>
</Stack>
</Stack>
</Surface>
))}
</Stack>
) : (
<Text size="sm" color="text-gray-500">No members found.</Text>
)}
</Box>
</Stack>
</Card>
</Stack>
);
}