website refactor
This commit is contained in:
@@ -61,25 +61,26 @@ export const LeagueCard = ({
|
||||
isFeatured
|
||||
}: LeagueCardProps) => {
|
||||
return (
|
||||
<Card
|
||||
variant="precision"
|
||||
onClick={onClick}
|
||||
<Card
|
||||
variant="precision"
|
||||
onClick={onClick}
|
||||
fullHeight
|
||||
padding="none"
|
||||
data-testid="league-card"
|
||||
>
|
||||
<Box height="8rem" position="relative" overflow="hidden">
|
||||
<Image
|
||||
src={coverUrl}
|
||||
alt={name}
|
||||
fullWidth
|
||||
fullHeight
|
||||
objectFit="cover"
|
||||
style={{ opacity: 0.4, filter: 'grayscale(0.2)' }}
|
||||
<Image
|
||||
src={coverUrl}
|
||||
alt={name}
|
||||
fullWidth
|
||||
fullHeight
|
||||
objectFit="cover"
|
||||
style={{ opacity: 0.4, filter: 'grayscale(0.2)' }}
|
||||
/>
|
||||
<Box
|
||||
position="absolute"
|
||||
inset={0}
|
||||
style={{ background: 'linear-gradient(to top, var(--ui-color-bg-base), transparent)' }}
|
||||
<Box
|
||||
position="absolute"
|
||||
inset={0}
|
||||
style={{ background: 'linear-gradient(to top, var(--ui-color-bg-base), transparent)' }}
|
||||
/>
|
||||
<Box position="absolute" top={3} left={3}>
|
||||
<Group gap={2}>
|
||||
@@ -93,12 +94,12 @@ export const LeagueCard = ({
|
||||
|
||||
<Stack padding={5} gap={5} flex={1}>
|
||||
<Stack direction="row" align="start" gap={4} marginTop="-2.5rem" position="relative" zIndex={10}>
|
||||
<Box
|
||||
width="4rem"
|
||||
height="4rem"
|
||||
bg="var(--ui-color-bg-surface)"
|
||||
rounded="lg"
|
||||
border
|
||||
<Box
|
||||
width="4rem"
|
||||
height="4rem"
|
||||
bg="var(--ui-color-bg-surface)"
|
||||
rounded="lg"
|
||||
border
|
||||
borderColor="var(--ui-color-border-default)"
|
||||
overflow="hidden"
|
||||
display="flex"
|
||||
@@ -112,7 +113,7 @@ export const LeagueCard = ({
|
||||
)}
|
||||
</Box>
|
||||
<Stack flex={1} gap={1} paddingTop="2.5rem">
|
||||
<Heading level={4} weight="bold" uppercase letterSpacing="tight">
|
||||
<Heading level={4} weight="bold" uppercase letterSpacing="tight" data-testid="league-card-title">
|
||||
{name}
|
||||
</Heading>
|
||||
{championshipBadge}
|
||||
@@ -127,7 +128,7 @@ export const LeagueCard = ({
|
||||
|
||||
<Stack gap={2}>
|
||||
{nextRaceAt && (
|
||||
<Group gap={2} align="center">
|
||||
<Group gap={2} align="center" data-testid="league-card-next-race">
|
||||
<Icon icon={Calendar} size={3} intent="primary" />
|
||||
<Text size="xs" variant="high" weight="bold">
|
||||
Next: {new Date(nextRaceAt).toLocaleDateString()} {new Date(nextRaceAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
||||
@@ -135,7 +136,7 @@ export const LeagueCard = ({
|
||||
</Group>
|
||||
)}
|
||||
{activeDriversCount !== undefined && activeDriversCount > 0 && (
|
||||
<Group gap={2} align="center">
|
||||
<Group gap={2} align="center" data-testid="league-card-active-drivers">
|
||||
<Icon icon={Users} size={3} intent="success" />
|
||||
<Text size="xs" variant="success" weight="bold">
|
||||
{activeDriversCount} Active Drivers
|
||||
@@ -153,34 +154,35 @@ export const LeagueCard = ({
|
||||
<Text size="sm" variant="high" font="mono" weight="bold">{usedSlots} / {maxSlots}</Text>
|
||||
</Group>
|
||||
<Box height="2px" bg="var(--ui-color-bg-surface-muted)" rounded="full" overflow="hidden">
|
||||
<Box
|
||||
height="100%"
|
||||
bg="var(--ui-color-intent-primary)"
|
||||
style={{
|
||||
<Box
|
||||
height="100%"
|
||||
bg="var(--ui-color-intent-primary)"
|
||||
style={{
|
||||
width: `${Math.min(fillPercentage, 100)}%`,
|
||||
boxShadow: `0 0 8px var(--ui-color-intent-primary)44`
|
||||
}}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
<Group gap={2} fullWidth>
|
||||
{onQuickJoin && (
|
||||
<Button
|
||||
size="xs"
|
||||
variant="primary"
|
||||
fullWidth
|
||||
<Button
|
||||
size="xs"
|
||||
variant="primary"
|
||||
fullWidth
|
||||
onClick={(e) => { e.stopPropagation(); onQuickJoin(e); }}
|
||||
icon={<Icon icon={UserPlus} size={3} />}
|
||||
data-testid="quick-join-button"
|
||||
>
|
||||
Join
|
||||
</Button>
|
||||
)}
|
||||
{onFollow && (
|
||||
<Button
|
||||
size="xs"
|
||||
variant="secondary"
|
||||
fullWidth
|
||||
<Button
|
||||
size="xs"
|
||||
variant="secondary"
|
||||
fullWidth
|
||||
onClick={(e) => { e.stopPropagation(); onFollow(e); }}
|
||||
icon={<Icon icon={Heart} size={3} />}
|
||||
>
|
||||
|
||||
@@ -27,7 +27,7 @@ export function NavLink({ href, label, icon, isActive, variant = 'sidebar', coll
|
||||
justifyContent={collapsed ? 'center' : 'space-between'}
|
||||
gap={collapsed ? 0 : 3}
|
||||
paddingX={collapsed ? 2 : 4}
|
||||
paddingY={3}
|
||||
paddingY={isTop ? 1.5 : 3}
|
||||
className={`
|
||||
relative transition-all duration-300 ease-out rounded-xl border w-full
|
||||
${isActive
|
||||
@@ -39,18 +39,18 @@ export function NavLink({ href, label, icon, isActive, variant = 'sidebar', coll
|
||||
`}
|
||||
title={collapsed ? label : undefined}
|
||||
>
|
||||
<Box display="flex" alignItems="center" gap={3} justifyContent={collapsed ? 'center' : 'start'} width={collapsed ? 'full' : 'auto'}>
|
||||
<Box display="flex" alignItems="center" gap={isTop ? 2 : 3} justifyContent={collapsed ? 'center' : 'start'} width={collapsed ? 'full' : 'auto'}>
|
||||
{icon && (
|
||||
<Icon
|
||||
icon={icon}
|
||||
size={5} // 20px
|
||||
size={isTop ? 4 : 5} // 16px for top, 20px for sidebar
|
||||
className={`transition-colors duration-200 ${isActive ? 'text-white' : 'text-text-med group-hover:text-white'}`}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!collapsed && (
|
||||
<Text
|
||||
size="sm"
|
||||
size={isTop ? "xs" : "sm"}
|
||||
weight="bold"
|
||||
variant="inherit"
|
||||
className={`tracking-wide transition-colors duration-200 ${isActive ? 'text-white' : 'text-text-med group-hover:text-white'}`}
|
||||
@@ -60,8 +60,8 @@ export function NavLink({ href, label, icon, isActive, variant = 'sidebar', coll
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Chevron on Hover/Active - Only when expanded */}
|
||||
{!collapsed && (
|
||||
{/* Chevron on Hover/Active - Only when expanded and not top nav */}
|
||||
{!collapsed && !isTop && (
|
||||
<Icon
|
||||
icon={ChevronRight}
|
||||
size={4}
|
||||
|
||||
29
apps/website/ui/PublicNavLogin.tsx
Normal file
29
apps/website/ui/PublicNavLogin.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import { Box } from './Box';
|
||||
import { Text } from './Text';
|
||||
import { Link } from './Link';
|
||||
import { routes } from '@/lib/routing/RouteConfig';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
import { Icon } from './Icon';
|
||||
|
||||
/**
|
||||
* PublicNavLogin is a login button component for public pages.
|
||||
* It displays a login button that redirects to the auth login page.
|
||||
*/
|
||||
export function PublicNavLogin() {
|
||||
return (
|
||||
<Link
|
||||
href={routes.auth.login}
|
||||
variant="inherit"
|
||||
data-testid="public-nav-login"
|
||||
className="group flex items-center gap-2 bg-white/5 hover:bg-white/10 border border-white/10 rounded-full pl-4 pr-2 py-1.5 transition-all duration-300 hover:border-primary-accent/50"
|
||||
>
|
||||
<Text size="sm" weight="medium" className="text-text-med group-hover:text-text-high">
|
||||
Login
|
||||
</Text>
|
||||
<Box className="w-6 h-6 rounded-full bg-primary-accent flex items-center justify-center text-white shadow-lg shadow-primary-accent/20 group-hover:scale-110 transition-transform">
|
||||
<Icon icon={ChevronDown} size={3.5} className="-rotate-90" />
|
||||
</Box>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
24
apps/website/ui/PublicNavSignup.tsx
Normal file
24
apps/website/ui/PublicNavSignup.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import { Box } from './Box';
|
||||
import { Text } from './Text';
|
||||
import { Link } from './Link';
|
||||
import { routes } from '@/lib/routing/RouteConfig';
|
||||
|
||||
/**
|
||||
* PublicNavSignup is a signup button component for public pages.
|
||||
* It displays a signup button that redirects to the auth signup page.
|
||||
*/
|
||||
export function PublicNavSignup() {
|
||||
return (
|
||||
<Link
|
||||
href={routes.auth.signup}
|
||||
variant="inherit"
|
||||
data-testid="public-nav-signup"
|
||||
className="group flex items-center gap-2 bg-primary-accent hover:bg-primary-accent/90 text-white rounded-full px-4 py-1.5 transition-all duration-300 hover:scale-105"
|
||||
>
|
||||
<Text size="sm" weight="medium" className="text-white">
|
||||
Sign Up
|
||||
</Text>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
45
apps/website/ui/PublicTopNav.tsx
Normal file
45
apps/website/ui/PublicTopNav.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
import { Box } from './Box';
|
||||
import { NavLink } from './NavLink';
|
||||
import { routes } from '@/lib/routing/RouteConfig';
|
||||
import { Trophy, Users, LayoutGrid, Flag, Calendar } from 'lucide-react';
|
||||
|
||||
interface PublicTopNavProps {
|
||||
pathname: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* PublicTopNav is a horizontal navigation component for public pages.
|
||||
* It displays navigation links to public routes like Leagues, Drivers, Teams, etc.
|
||||
*/
|
||||
export function PublicTopNav({ pathname }: PublicTopNavProps) {
|
||||
const navItems = [
|
||||
{ label: 'Leagues', href: routes.public.leagues, icon: Trophy },
|
||||
{ label: 'Drivers', href: routes.public.drivers, icon: Users },
|
||||
{ label: 'Teams', href: routes.public.teams, icon: Flag },
|
||||
{ label: 'Leaderboards', href: routes.public.leaderboards, icon: LayoutGrid },
|
||||
{ label: 'Races', href: routes.public.races, icon: Calendar },
|
||||
];
|
||||
|
||||
return (
|
||||
<Box
|
||||
as="nav"
|
||||
data-testid="public-top-nav"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
gap={4}
|
||||
width="full"
|
||||
>
|
||||
{navItems.map((item) => (
|
||||
<NavLink
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
label={item.label}
|
||||
icon={item.icon}
|
||||
isActive={pathname === item.href}
|
||||
variant="top"
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user