website refactor

This commit is contained in:
2026-01-17 15:46:55 +01:00
parent 4d5ce9bfd6
commit 72a626ce71
346 changed files with 19308 additions and 8605 deletions

View File

@@ -1,14 +1,15 @@
'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 { Text } from '@/ui/Text';
import { Heading } from '@/ui/Heading';
import { Surface } from '@/ui/Surface';
import { Button } from '@/ui/Button';
import { Shield, UserPlus, UserMinus } from 'lucide-react';
import { Icon } from '@/ui/Icon';
import { Table, TableHead, TableBody, TableRow, TableHeader, TableCell } from '@/ui/Table';
import type { MembershipRole } from '@/lib/types/MembershipRole';
import type { LeagueRosterAdminViewData } from '@/lib/view-data/LeagueRosterAdminViewData';
@@ -36,119 +37,131 @@ export function RosterAdminTemplate({
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>
)}
<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>
</Card>
{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>
);
}