website refactor
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
import { LeagueListItem } from '@/components/leagues/LeagueListItem';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { ProfileSection } from './ProfileSection';
|
||||
import React from 'react';
|
||||
|
||||
@@ -22,7 +23,7 @@ interface MembershipPanelProps {
|
||||
|
||||
export function MembershipPanel({ ownedLeagues, memberLeagues }: MembershipPanelProps) {
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}>
|
||||
<Stack gap={8}>
|
||||
<ProfileSection
|
||||
title="Leagues You Own"
|
||||
description="Manage the leagues you have created and lead."
|
||||
@@ -34,11 +35,11 @@ export function MembershipPanel({ ownedLeagues, memberLeagues }: MembershipPanel
|
||||
</Text>
|
||||
</Card>
|
||||
) : (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
|
||||
<Stack gap={3}>
|
||||
{ownedLeagues.map((league) => (
|
||||
<LeagueListItem key={league.leagueId} league={league} isAdmin />
|
||||
))}
|
||||
</div>
|
||||
</Stack>
|
||||
)}
|
||||
</ProfileSection>
|
||||
|
||||
@@ -53,13 +54,13 @@ export function MembershipPanel({ ownedLeagues, memberLeagues }: MembershipPanel
|
||||
</Text>
|
||||
</Card>
|
||||
) : (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
|
||||
<Stack gap={3}>
|
||||
{memberLeagues.map((league) => (
|
||||
<LeagueListItem key={league.leagueId} league={league} />
|
||||
))}
|
||||
</div>
|
||||
</Stack>
|
||||
)}
|
||||
</ProfileSection>
|
||||
</div>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
import { Panel } from '@/ui/Panel';
|
||||
import { Select } from '@/ui/Select';
|
||||
import { Toggle } from '@/ui/Toggle';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { ProfileStatsGroup, ProfileStat } from '@/ui/ProfileHero';
|
||||
import React from 'react';
|
||||
|
||||
@@ -22,7 +24,7 @@ export function PreferencesPanel({ preferences, isEditing, onUpdate }: Preferenc
|
||||
if (isEditing) {
|
||||
return (
|
||||
<Panel title="Racing Preferences">
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '1.5rem' }}>
|
||||
<Stack gap={6}>
|
||||
<Select
|
||||
label="Favorite Car Class"
|
||||
value={preferences.favoriteCarClass}
|
||||
@@ -45,19 +47,21 @@ export function PreferencesPanel({ preferences, isEditing, onUpdate }: Preferenc
|
||||
]}
|
||||
/>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem', paddingTop: '0.5rem' }}>
|
||||
<Toggle
|
||||
label="Public Profile"
|
||||
checked={preferences.showProfile}
|
||||
onChange={(checked) => onUpdate?.({ showProfile: checked })}
|
||||
/>
|
||||
<Toggle
|
||||
label="Show Race History"
|
||||
checked={preferences.showHistory}
|
||||
onChange={(checked) => onUpdate?.({ showHistory: checked })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Box paddingTop={2}>
|
||||
<Stack gap={3}>
|
||||
<Toggle
|
||||
label="Public Profile"
|
||||
checked={preferences.showProfile}
|
||||
onChange={(checked) => onUpdate?.({ showProfile: checked })}
|
||||
/>
|
||||
<Toggle
|
||||
label="Show Race History"
|
||||
checked={preferences.showHistory}
|
||||
onChange={(checked) => onUpdate?.({ showHistory: checked })}
|
||||
/>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@ import { Panel } from '@/ui/Panel';
|
||||
import { Input } from '@/ui/Input';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { TextArea } from '@/ui/TextArea';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Group } from '@/ui/Group';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { ProfileStat } from '@/ui/ProfileHero';
|
||||
import React from 'react';
|
||||
|
||||
@@ -22,7 +25,7 @@ export function ProfileDetailsPanel({ driver, isEditing, onUpdate }: ProfileDeta
|
||||
if (isEditing) {
|
||||
return (
|
||||
<Panel title="Profile Details">
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '1.5rem' }}>
|
||||
<Stack gap={6}>
|
||||
<Input
|
||||
label="Nationality (ISO Code)"
|
||||
value={driver.country}
|
||||
@@ -35,31 +38,31 @@ export function ProfileDetailsPanel({ driver, isEditing, onUpdate }: ProfileDeta
|
||||
onChange={(e) => onUpdate?.({ bio: e.target.value })}
|
||||
placeholder="Tell the community about your racing career..."
|
||||
/>
|
||||
</div>
|
||||
</Stack>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Panel title="Profile Details">
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '1.5rem' }}>
|
||||
<div>
|
||||
<Text size="xs" variant="low" weight="bold" uppercase block marginBottom={1}>Nationality</Text>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
|
||||
<Stack gap={6}>
|
||||
<Stack gap={1}>
|
||||
<Text size="xs" variant="low" weight="bold" uppercase block>Nationality</Text>
|
||||
<Group gap={2}>
|
||||
<Text size="xl">
|
||||
{CountryFlagDisplay.fromCountryCode(driver.country).toString()}
|
||||
</Text>
|
||||
<Text variant="med">{driver.country}</Text>
|
||||
</div>
|
||||
</div>
|
||||
</Group>
|
||||
</Stack>
|
||||
|
||||
<div>
|
||||
<Text size="xs" variant="low" weight="bold" uppercase block marginBottom={1}>Bio</Text>
|
||||
<Stack gap={1}>
|
||||
<Text size="xs" variant="low" weight="bold" uppercase block>Bio</Text>
|
||||
<Text variant="med" leading="relaxed">
|
||||
{driver.bio || 'No bio provided.'}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@ import { Heading } from '@/ui/Heading';
|
||||
import { Image } from '@/ui/Image';
|
||||
import { ProfileHero, ProfileAvatar, ProfileStatsGroup, ProfileStat } from '@/ui/ProfileHero';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Group } from '@/ui/Group';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Calendar, Globe, Star, Trophy, UserPlus } from 'lucide-react';
|
||||
import React from 'react';
|
||||
|
||||
@@ -37,7 +40,7 @@ export function ProfileHeader({
|
||||
}: ProfileHeaderProps) {
|
||||
return (
|
||||
<ProfileHero variant="muted">
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '2rem', flexWrap: 'wrap' }}>
|
||||
<Box display="flex" alignItems="center" gap={8} flexWrap="wrap">
|
||||
<ProfileAvatar>
|
||||
<Image
|
||||
src={driver.avatarUrl || mediaConfig.avatars.defaultFallback}
|
||||
@@ -48,27 +51,29 @@ export function ProfileHeader({
|
||||
/>
|
||||
</ProfileAvatar>
|
||||
|
||||
<div style={{ flex: 1, minWidth: '200px' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem', marginBottom: '0.25rem' }}>
|
||||
<Heading level={1}>{driver.name}</Heading>
|
||||
<Text size="2xl" aria-label={`Country: ${driver.country}`}>
|
||||
{CountryFlagDisplay.fromCountryCode(driver.country).toString()}
|
||||
</Text>
|
||||
</div>
|
||||
<Stack flex={1} minWidth="200px" gap={1}>
|
||||
<Box marginBottom={1}>
|
||||
<Group gap={3}>
|
||||
<Heading level={1}>{driver.name}</Heading>
|
||||
<Text size="2xl" aria-label={`Country: ${driver.country}`}>
|
||||
{CountryFlagDisplay.fromCountryCode(driver.country).toString()}
|
||||
</Text>
|
||||
</Group>
|
||||
</Box>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.375rem' }}>
|
||||
<Group gap={4}>
|
||||
<Group gap={1.5}>
|
||||
<Globe size={14} color="var(--ui-color-text-low)" />
|
||||
<Text size="xs" font="mono" variant="low">ID: {driver.iracingId}</Text>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.375rem' }}>
|
||||
</Group>
|
||||
<Group gap={1.5}>
|
||||
<Calendar size={14} color="var(--ui-color-text-low)" />
|
||||
<Text size="xs" variant="low">
|
||||
Joined {new Date(driver.joinedAt).toLocaleDateString('en-US', { month: 'short', year: 'numeric' })}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
|
||||
<ProfileStatsGroup>
|
||||
{stats && (
|
||||
@@ -80,7 +85,7 @@ export function ProfileHeader({
|
||||
</ProfileStatsGroup>
|
||||
|
||||
{!isOwnProfile && onAddFriend && (
|
||||
<div style={{ marginLeft: 'auto' }}>
|
||||
<Box marginLeft="auto">
|
||||
<Button
|
||||
variant={friendRequestSent ? 'secondary' : 'primary'}
|
||||
onClick={onAddFriend}
|
||||
@@ -90,9 +95,9 @@ export function ProfileHeader({
|
||||
>
|
||||
{friendRequestSent ? 'Request Sent' : 'Add Friend'}
|
||||
</Button>
|
||||
</div>
|
||||
</Box>
|
||||
)}
|
||||
</div>
|
||||
</Box>
|
||||
</ProfileHero>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { SectionHeader } from '@/ui/SectionHeader';
|
||||
import { Box } from '@/ui/Box';
|
||||
import React from 'react';
|
||||
|
||||
interface ProfileSectionProps {
|
||||
@@ -12,14 +13,14 @@ interface ProfileSectionProps {
|
||||
|
||||
export function ProfileSection({ title, description, action, children }: ProfileSectionProps) {
|
||||
return (
|
||||
<section style={{ marginBottom: '2rem' }}>
|
||||
<Box as="section" marginBottom={8}>
|
||||
<SectionHeader
|
||||
title={title}
|
||||
description={description}
|
||||
actions={action}
|
||||
variant="minimal"
|
||||
/>
|
||||
<div>{children}</div>
|
||||
</section>
|
||||
<Box>{children}</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,10 @@ import { Icon } from '@/ui/Icon';
|
||||
import { Image } from '@/ui/Image';
|
||||
import { Link } from '@/ui/Link';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Group } from '@/ui/Group';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Surface } from '@/ui/Surface';
|
||||
import { UserDropdown, UserDropdownHeader, UserDropdownItem, UserDropdownFooter } from '@/ui/UserDropdown';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import {
|
||||
@@ -143,7 +147,7 @@ export function UserPill() {
|
||||
// Handle unauthenticated users
|
||||
if (!session) {
|
||||
return (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
|
||||
<Group gap={2}>
|
||||
<Link
|
||||
href={routes.auth.login}
|
||||
variant="secondary"
|
||||
@@ -156,7 +160,7 @@ export function UserPill() {
|
||||
>
|
||||
Get Started
|
||||
</Link>
|
||||
</div>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -185,7 +189,7 @@ export function UserPill() {
|
||||
} as Record<string, 'primary' | 'success' | 'warning' | 'critical'>)[demoRole || 'driver'] : 'low';
|
||||
|
||||
return (
|
||||
<div style={{ position: 'relative', display: 'inline-flex', alignItems: 'center' }} data-user-pill>
|
||||
<Box position="relative" display="inline-flex" alignItems="center" data-user-pill>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsMenuOpen((open) => !open)}
|
||||
@@ -201,26 +205,26 @@ export function UserPill() {
|
||||
}}
|
||||
>
|
||||
{/* Avatar */}
|
||||
<div style={{ position: 'relative' }}>
|
||||
<Box position="relative">
|
||||
{avatarUrl ? (
|
||||
<div style={{ width: '2rem', height: '2rem', borderRadius: '9999px', overflow: 'hidden', border: '1px solid var(--ui-color-border-default)' }}>
|
||||
<Surface width="2rem" height="2rem" rounded="full" overflow="hidden" border>
|
||||
<Image
|
||||
src={avatarUrl}
|
||||
alt={displayName}
|
||||
objectFit="cover"
|
||||
/>
|
||||
</div>
|
||||
</Surface>
|
||||
) : (
|
||||
<div style={{ width: '2rem', height: '2rem', borderRadius: '9999px', backgroundColor: 'var(--ui-color-intent-primary)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<Surface width="2rem" height="2rem" rounded="full" variant="gradient-blue" display="flex" alignItems="center" justifyContent="center">
|
||||
<Text size="xs" weight="bold" variant="high">
|
||||
{displayName[0]?.toUpperCase() || 'U'}
|
||||
</Text>
|
||||
</div>
|
||||
</Surface>
|
||||
)}
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
{/* Info */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'start' }}>
|
||||
<Stack align="start" gap={0}>
|
||||
<Text size="xs" weight="semibold" variant="high" truncate style={{ maxWidth: '100px' }}>
|
||||
{displayName}
|
||||
</Text>
|
||||
@@ -229,7 +233,7 @@ export function UserPill() {
|
||||
{roleLabel}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
</Stack>
|
||||
|
||||
{/* Chevron */}
|
||||
<Icon icon={ChevronDown} size={3.5} intent="low" />
|
||||
@@ -237,32 +241,32 @@ export function UserPill() {
|
||||
|
||||
<UserDropdown isOpen={isMenuOpen}>
|
||||
<UserDropdownHeader variant={isDemo ? 'demo' : 'default'}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
|
||||
<Group gap={3}>
|
||||
{avatarUrl ? (
|
||||
<div style={{ width: '2.5rem', height: '2.5rem', borderRadius: '0.5rem', overflow: 'hidden', border: '1px solid var(--ui-color-border-default)' }}>
|
||||
<Surface width="2.5rem" height="2.5rem" rounded="md" overflow="hidden" border>
|
||||
<Image
|
||||
src={avatarUrl}
|
||||
alt={displayName}
|
||||
objectFit="cover"
|
||||
/>
|
||||
</div>
|
||||
</Surface>
|
||||
) : (
|
||||
<div style={{ width: '2.5rem', height: '2.5rem', borderRadius: '0.5rem', backgroundColor: 'var(--ui-color-intent-primary)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<Surface width="2.5rem" height="2.5rem" rounded="md" variant="gradient-blue" display="flex" alignItems="center" justifyContent="center">
|
||||
<Text size="xs" weight="bold" variant="high">
|
||||
{displayName[0]?.toUpperCase() || 'U'}
|
||||
</Text>
|
||||
</div>
|
||||
</Surface>
|
||||
)}
|
||||
<div>
|
||||
<Stack gap={0}>
|
||||
<Text size="sm" weight="semibold" variant="high" block>{displayName}</Text>
|
||||
{roleLabel && (
|
||||
<Text size="xs" variant="low" block>{roleLabel}</Text>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Stack>
|
||||
</Group>
|
||||
</UserDropdownHeader>
|
||||
|
||||
<div style={{ padding: '0.25rem 0' }}>
|
||||
<Box paddingY={1}>
|
||||
{hasAdminAccess && (
|
||||
<UserDropdownItem href="/admin" icon={Shield} label="Admin Area" intent="primary" onClick={() => setIsMenuOpen(false)} />
|
||||
)}
|
||||
@@ -280,7 +284,7 @@ export function UserPill() {
|
||||
<UserDropdownItem href={routes.protected.profileLiveries} icon={Paintbrush} label="Liveries" onClick={() => setIsMenuOpen(false)} />
|
||||
<UserDropdownItem href={routes.protected.profileSponsorshipRequests} icon={Handshake} label="Sponsorship Requests" intent="success" onClick={() => setIsMenuOpen(false)} />
|
||||
<UserDropdownItem href={routes.protected.profileSettings} icon={Settings} label="Settings" onClick={() => setIsMenuOpen(false)} />
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<UserDropdownFooter>
|
||||
<UserDropdownItem
|
||||
@@ -291,6 +295,6 @@ export function UserPill() {
|
||||
/>
|
||||
</UserDropdownFooter>
|
||||
</UserDropdown>
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user