website refactor
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Panel } from '@/ui/Panel';
|
||||
import { AccountItem } from '@/ui/AccountItem';
|
||||
import { Badge } from '@/ui/Badge';
|
||||
import { Globe, Link as LinkIcon } from 'lucide-react';
|
||||
import React from 'react';
|
||||
|
||||
interface ConnectedAccountsPanelProps {
|
||||
iracingId?: string | number;
|
||||
@@ -14,56 +14,35 @@ interface ConnectedAccountsPanelProps {
|
||||
|
||||
export function ConnectedAccountsPanel({ iracingId, onConnectIRacing }: ConnectedAccountsPanelProps) {
|
||||
return (
|
||||
<section aria-labelledby="connected-accounts-heading">
|
||||
<Card>
|
||||
<Stack gap={6}>
|
||||
<Heading level={3} id="connected-accounts-heading" fontSize="1.125rem">Connected Accounts</Heading>
|
||||
|
||||
<Stack gap={4} className="divide-y divide-border-gray/30">
|
||||
<Stack direction="row" justify="between" align="center" pt={0}>
|
||||
<Stack direction="row" align="center" gap={3}>
|
||||
<Stack backgroundColor="#1e293b" p={2} rounded="md">
|
||||
<Globe size={20} color="#4ED4E0" />
|
||||
</Stack>
|
||||
<Stack gap={0.5}>
|
||||
<Text weight="bold" size="sm">iRacing</Text>
|
||||
<Text size="xs" color="#9ca3af">
|
||||
{iracingId ? `Connected ID: ${iracingId}` : 'Not connected'}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
{!iracingId && (
|
||||
<Button size="sm" variant="secondary" onClick={onConnectIRacing}>
|
||||
Connect
|
||||
</Button>
|
||||
)}
|
||||
{iracingId && (
|
||||
<Stack backgroundColor="rgba(16, 185, 129, 0.1)" px={2} py={1} rounded="full">
|
||||
<Text size="xs" color="#10b981" weight="bold">Verified</Text>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
<Panel title="Connected Accounts">
|
||||
<AccountItem
|
||||
icon={Globe}
|
||||
title="iRacing"
|
||||
description={iracingId ? `Connected ID: ${iracingId}` : 'Not connected'}
|
||||
intent="telemetry"
|
||||
action={
|
||||
iracingId ? (
|
||||
<Badge variant="success">Verified</Badge>
|
||||
) : (
|
||||
<Button size="sm" variant="secondary" onClick={onConnectIRacing}>
|
||||
Connect
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
<Stack direction="row" justify="between" align="center" pt={4}>
|
||||
<Stack direction="row" align="center" gap={3}>
|
||||
<Stack backgroundColor="#1e293b" p={2} rounded="md">
|
||||
<LinkIcon size={20} color="#198CFF" />
|
||||
</Stack>
|
||||
<Stack gap={0.5}>
|
||||
<Text weight="bold" size="sm">Discord</Text>
|
||||
<Text size="xs" color="#9ca3af">Connect for notifications</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
<Button size="sm" variant="secondary">
|
||||
Connect
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Card>
|
||||
</section>
|
||||
<AccountItem
|
||||
icon={LinkIcon}
|
||||
title="Discord"
|
||||
description="Connect for notifications"
|
||||
intent="primary"
|
||||
action={
|
||||
<Button size="sm" variant="secondary">
|
||||
Connect
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
import { LeagueListItem } from '@/components/leagues/LeagueListItem';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { ProfileSection } from './ProfileSection';
|
||||
import React from 'react';
|
||||
|
||||
interface League {
|
||||
leagueId: string;
|
||||
@@ -22,23 +22,23 @@ interface MembershipPanelProps {
|
||||
|
||||
export function MembershipPanel({ ownedLeagues, memberLeagues }: MembershipPanelProps) {
|
||||
return (
|
||||
<Stack gap={8}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}>
|
||||
<ProfileSection
|
||||
title="Leagues You Own"
|
||||
description="Manage the leagues you have created and lead."
|
||||
>
|
||||
{ownedLeagues.length === 0 ? (
|
||||
<Card>
|
||||
<Text size="sm" color="text-gray-400">
|
||||
<Text size="sm" variant="low">
|
||||
You don't own any leagues yet.
|
||||
</Text>
|
||||
</Card>
|
||||
) : (
|
||||
<Stack gap={3}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
|
||||
{ownedLeagues.map((league) => (
|
||||
<LeagueListItem key={league.leagueId} league={league} isAdmin />
|
||||
))}
|
||||
</Stack>
|
||||
</div>
|
||||
)}
|
||||
</ProfileSection>
|
||||
|
||||
@@ -48,18 +48,18 @@ export function MembershipPanel({ ownedLeagues, memberLeagues }: MembershipPanel
|
||||
>
|
||||
{memberLeagues.length === 0 ? (
|
||||
<Card>
|
||||
<Text size="sm" color="text-gray-400">
|
||||
<Text size="sm" variant="low">
|
||||
You're not a member of any other leagues yet.
|
||||
</Text>
|
||||
</Card>
|
||||
) : (
|
||||
<Stack gap={3}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
|
||||
{memberLeagues.map((league) => (
|
||||
<LeagueListItem key={league.leagueId} league={league} />
|
||||
))}
|
||||
</Stack>
|
||||
</div>
|
||||
)}
|
||||
</ProfileSection>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
'use client';
|
||||
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Panel } from '@/ui/Panel';
|
||||
import { Select } from '@/ui/Select';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Toggle } from '@/ui/Toggle';
|
||||
import { ProfileStatsGroup, ProfileStat } from '@/ui/ProfileHero';
|
||||
import React from 'react';
|
||||
|
||||
interface PreferencesPanelProps {
|
||||
preferences: {
|
||||
@@ -22,75 +21,54 @@ interface PreferencesPanelProps {
|
||||
export function PreferencesPanel({ preferences, isEditing, onUpdate }: PreferencesPanelProps) {
|
||||
if (isEditing) {
|
||||
return (
|
||||
<section aria-labelledby="preferences-heading">
|
||||
<Card>
|
||||
<Stack gap={6}>
|
||||
<Heading level={3} id="preferences-heading" fontSize="1.125rem">Racing Preferences</Heading>
|
||||
|
||||
<Stack gap={4}>
|
||||
<Select
|
||||
label="Favorite Car Class"
|
||||
value={preferences.favoriteCarClass}
|
||||
onChange={(e) => onUpdate?.({ favoriteCarClass: e.target.value })}
|
||||
options={[
|
||||
{ value: 'GT3', label: 'GT3' },
|
||||
{ value: 'GT4', label: 'GT4' },
|
||||
{ value: 'Formula', label: 'Formula' },
|
||||
{ value: 'LMP2', label: 'LMP2' },
|
||||
]}
|
||||
/>
|
||||
<Select
|
||||
label="Competitive Level"
|
||||
value={preferences.competitiveLevel}
|
||||
onChange={(e) => onUpdate?.({ competitiveLevel: e.target.value })}
|
||||
options={[
|
||||
{ value: 'casual', label: 'Casual' },
|
||||
{ value: 'competitive', label: 'Competitive' },
|
||||
{ value: 'professional', label: 'Professional' },
|
||||
]}
|
||||
/>
|
||||
|
||||
<Stack gap={3} pt={2}>
|
||||
<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>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Card>
|
||||
</section>
|
||||
<Panel title="Racing Preferences">
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '1.5rem' }}>
|
||||
<Select
|
||||
label="Favorite Car Class"
|
||||
value={preferences.favoriteCarClass}
|
||||
onChange={(e) => onUpdate?.({ favoriteCarClass: e.target.value })}
|
||||
options={[
|
||||
{ value: 'GT3', label: 'GT3' },
|
||||
{ value: 'GT4', label: 'GT4' },
|
||||
{ value: 'Formula', label: 'Formula' },
|
||||
{ value: 'LMP2', label: 'LMP2' },
|
||||
]}
|
||||
/>
|
||||
<Select
|
||||
label="Competitive Level"
|
||||
value={preferences.competitiveLevel}
|
||||
onChange={(e) => onUpdate?.({ competitiveLevel: e.target.value })}
|
||||
options={[
|
||||
{ value: 'casual', label: 'Casual' },
|
||||
{ value: 'competitive', label: 'Competitive' },
|
||||
{ value: 'professional', label: 'Professional' },
|
||||
]}
|
||||
/>
|
||||
|
||||
<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>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<section aria-labelledby="preferences-heading">
|
||||
<Card>
|
||||
<Stack gap={6}>
|
||||
<Heading level={3} id="preferences-heading" fontSize="1.125rem">Racing Preferences</Heading>
|
||||
|
||||
<Stack direction="row" gap={8} wrap>
|
||||
<Stack gap={1}>
|
||||
<Text size="xs" color="#6b7280" weight="bold" letterSpacing="0.05em" uppercase>Car Class</Text>
|
||||
<Text color="#d1d5db">{preferences.favoriteCarClass}</Text>
|
||||
</Stack>
|
||||
<Stack gap={1}>
|
||||
<Text size="xs" color="#6b7280" weight="bold" letterSpacing="0.05em" uppercase>Level</Text>
|
||||
<Text color="#d1d5db" capitalize>{preferences.competitiveLevel}</Text>
|
||||
</Stack>
|
||||
<Stack gap={1}>
|
||||
<Text size="xs" color="#6b7280" weight="bold" letterSpacing="0.05em" uppercase>Visibility</Text>
|
||||
<Text color="#d1d5db">{preferences.showProfile ? 'Public' : 'Private'}</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Card>
|
||||
</section>
|
||||
<Panel title="Racing Preferences">
|
||||
<ProfileStatsGroup>
|
||||
<ProfileStat label="Car Class" value={preferences.favoriteCarClass} intent="primary" />
|
||||
<ProfileStat label="Level" value={preferences.competitiveLevel} intent="telemetry" />
|
||||
<ProfileStat label="Visibility" value={preferences.showProfile ? 'Public' : 'Private'} intent="low" />
|
||||
</ProfileStatsGroup>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
|
||||
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
import { Panel } from '@/ui/Panel';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { User } from 'lucide-react';
|
||||
import React from 'react';
|
||||
|
||||
interface ProfileBioProps {
|
||||
bio: string;
|
||||
@@ -13,13 +8,10 @@ interface ProfileBioProps {
|
||||
|
||||
export function ProfileBio({ bio }: ProfileBioProps) {
|
||||
return (
|
||||
<Card>
|
||||
<Box mb={3}>
|
||||
<Heading level={2} icon={<Icon icon={User} size={5} color="#3b82f6" />}>
|
||||
About
|
||||
</Heading>
|
||||
</Box>
|
||||
<Text color="text-gray-300">{bio}</Text>
|
||||
</Card>
|
||||
<Panel
|
||||
title="About"
|
||||
>
|
||||
<Text variant="med">{bio}</Text>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
'use client';
|
||||
|
||||
import { CountryFlagDisplay } from '@/lib/display-objects/CountryFlagDisplay';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Panel } from '@/ui/Panel';
|
||||
import { Input } from '@/ui/Input';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { TextArea } from '@/ui/TextArea';
|
||||
import { ProfileStat } from '@/ui/ProfileHero';
|
||||
import React from 'react';
|
||||
|
||||
interface ProfileDetailsPanelProps {
|
||||
driver: {
|
||||
@@ -21,60 +21,45 @@ interface ProfileDetailsPanelProps {
|
||||
export function ProfileDetailsPanel({ driver, isEditing, onUpdate }: ProfileDetailsPanelProps) {
|
||||
if (isEditing) {
|
||||
return (
|
||||
<section aria-labelledby="profile-details-heading">
|
||||
<Card>
|
||||
<Stack gap={6}>
|
||||
<Heading level={3} id="profile-details-heading" fontSize="1.125rem">Profile Details</Heading>
|
||||
<Stack gap={4}>
|
||||
<Input
|
||||
label="Nationality (ISO Code)"
|
||||
value={driver.country}
|
||||
onChange={(e) => onUpdate?.({ country: e.target.value })}
|
||||
placeholder="e.g. US, GB, DE"
|
||||
maxLength={2}
|
||||
/>
|
||||
<TextArea
|
||||
label="Bio"
|
||||
value={driver.bio || ''}
|
||||
onChange={(e) => onUpdate?.({ bio: e.target.value })}
|
||||
placeholder="Tell the community about your racing career..."
|
||||
rows={4}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Card>
|
||||
</section>
|
||||
<Panel title="Profile Details">
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '1.5rem' }}>
|
||||
<Input
|
||||
label="Nationality (ISO Code)"
|
||||
value={driver.country}
|
||||
onChange={(e) => onUpdate?.({ country: e.target.value })}
|
||||
placeholder="e.g. US, GB, DE"
|
||||
/>
|
||||
<TextArea
|
||||
label="Bio"
|
||||
value={driver.bio || ''}
|
||||
onChange={(e) => onUpdate?.({ bio: e.target.value })}
|
||||
placeholder="Tell the community about your racing career..."
|
||||
/>
|
||||
</div>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<section aria-labelledby="profile-details-heading">
|
||||
<Card>
|
||||
<Stack gap={6}>
|
||||
<Stack direction="row" justify="between" align="center">
|
||||
<Heading level={3} id="profile-details-heading" fontSize="1.125rem">Profile Details</Heading>
|
||||
</Stack>
|
||||
|
||||
<Stack gap={4}>
|
||||
<Stack gap={1}>
|
||||
<Text size="xs" color="#6b7280" weight="bold" letterSpacing="0.05em" uppercase>Nationality</Text>
|
||||
<Stack direction="row" align="center" gap={2}>
|
||||
<Text size="xl">
|
||||
{CountryFlagDisplay.fromCountryCode(driver.country).toString()}
|
||||
</Text>
|
||||
<Text color="#d1d5db">{driver.country}</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<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' }}>
|
||||
<Text size="xl">
|
||||
{CountryFlagDisplay.fromCountryCode(driver.country).toString()}
|
||||
</Text>
|
||||
<Text variant="med">{driver.country}</Text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Stack gap={1}>
|
||||
<Text size="xs" color="#6b7280" weight="bold" letterSpacing="0.05em" uppercase>Bio</Text>
|
||||
<Text color="#d1d5db" lineHeight="relaxed">
|
||||
{driver.bio || 'No bio provided.'}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Card>
|
||||
</section>
|
||||
<div>
|
||||
<Text size="xs" variant="low" weight="bold" uppercase block marginBottom={1}>Bio</Text>
|
||||
<Text variant="med" leading="relaxed">
|
||||
{driver.bio || 'No bio provided.'}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@ import { CountryFlagDisplay } from '@/lib/display-objects/CountryFlagDisplay';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Image } from '@/ui/Image';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Surface } from '@/ui/primitives/Surface';
|
||||
import { ProfileHero, ProfileAvatar, ProfileStatsGroup, ProfileStat } from '@/ui/ProfileHero';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Calendar, Globe, Star, Trophy, UserPlus } from 'lucide-react';
|
||||
import React from 'react';
|
||||
|
||||
interface ProfileHeaderProps {
|
||||
driver: {
|
||||
@@ -36,103 +36,63 @@ export function ProfileHeader({
|
||||
isOwnProfile,
|
||||
}: ProfileHeaderProps) {
|
||||
return (
|
||||
<header>
|
||||
<Surface variant="muted" rounded="xl" border padding={6} backgroundColor="#141619" borderColor="#23272B">
|
||||
<Stack direction="row" align="center" gap={8} wrap>
|
||||
{/* Avatar with telemetry-style border */}
|
||||
<Stack position="relative">
|
||||
<Stack
|
||||
width="6rem"
|
||||
height="6rem"
|
||||
rounded="md"
|
||||
border
|
||||
borderColor="#23272B"
|
||||
p={0.5}
|
||||
backgroundColor="#0C0D0F"
|
||||
>
|
||||
<Stack width="100%" height="100%" rounded="sm" overflow="hidden">
|
||||
<Image
|
||||
src={driver.avatarUrl || mediaConfig.avatars.defaultFallback}
|
||||
alt={driver.name}
|
||||
width={96}
|
||||
height={96}
|
||||
objectFit="cover"
|
||||
fullWidth
|
||||
fullHeight
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<ProfileHero variant="muted">
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '2rem', flexWrap: 'wrap' }}>
|
||||
<ProfileAvatar>
|
||||
<Image
|
||||
src={driver.avatarUrl || mediaConfig.avatars.defaultFallback}
|
||||
alt={driver.name}
|
||||
width={96}
|
||||
height={96}
|
||||
objectFit="cover"
|
||||
/>
|
||||
</ProfileAvatar>
|
||||
|
||||
{/* Driver Info */}
|
||||
<Stack flexGrow={1} minWidth="0">
|
||||
<Stack direction="row" align="center" gap={3} mb={1}>
|
||||
<Heading level={1} fontSize="1.5rem">
|
||||
{driver.name}
|
||||
</Heading>
|
||||
<Text size="2xl" aria-label={`Country: ${driver.country}`}>
|
||||
{CountryFlagDisplay.fromCountryCode(driver.country).toString()}
|
||||
<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>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.375rem' }}>
|
||||
<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' }}>
|
||||
<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>
|
||||
</Stack>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Stack direction="row" align="center" gap={4} color="#9ca3af">
|
||||
<Stack direction="row" align="center" gap={1.5}>
|
||||
<Globe size={14} />
|
||||
<Text size="xs" font="mono">ID: {driver.iracingId}</Text>
|
||||
</Stack>
|
||||
<Stack width="1px" height="12px" backgroundColor="#23272B" />
|
||||
<Stack direction="row" align="center" gap={1.5}>
|
||||
<Calendar size={14} />
|
||||
<Text size="xs">
|
||||
Joined {new Date(driver.joinedAt).toLocaleDateString('en-US', { month: 'short', year: 'numeric' })}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
{/* Stats Grid */}
|
||||
<Stack direction="row" align="center" gap={6}>
|
||||
{stats && (
|
||||
<>
|
||||
<Stack>
|
||||
<Stack gap={0.5}>
|
||||
<Text size="xs" color="#6b7280" weight="bold" letterSpacing="0.05em">RATING</Text>
|
||||
<Stack direction="row" align="center" gap={1.5}>
|
||||
<Star size={14} color="#198CFF" />
|
||||
<Text font="mono" size="lg" weight="bold" color="#198CFF">{stats.rating}</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack width="1px" height="32px" backgroundColor="#23272B" />
|
||||
<Stack>
|
||||
<Stack gap={0.5}>
|
||||
<Text size="xs" color="#6b7280" weight="bold" letterSpacing="0.05em">GLOBAL RANK</Text>
|
||||
<Stack direction="row" align="center" gap={1.5}>
|
||||
<Trophy size={14} color="#FFBE4D" />
|
||||
<Text font="mono" size="lg" weight="bold" color="#FFBE4D">#{globalRank}</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
{/* Actions */}
|
||||
{!isOwnProfile && onAddFriend && (
|
||||
<Stack ml="auto">
|
||||
<Button
|
||||
variant={friendRequestSent ? 'secondary' : 'primary'}
|
||||
onClick={onAddFriend}
|
||||
disabled={friendRequestSent}
|
||||
size="sm"
|
||||
icon={<UserPlus size={16} />}
|
||||
>
|
||||
{friendRequestSent ? 'Request Sent' : 'Add Friend'}
|
||||
</Button>
|
||||
</Stack>
|
||||
<ProfileStatsGroup>
|
||||
{stats && (
|
||||
<React.Fragment>
|
||||
<ProfileStat label="RATING" value={stats.rating} intent="primary" />
|
||||
<ProfileStat label="GLOBAL RANK" value={`#${globalRank}`} intent="warning" />
|
||||
</React.Fragment>
|
||||
)}
|
||||
</Stack>
|
||||
</Surface>
|
||||
</header>
|
||||
</ProfileStatsGroup>
|
||||
|
||||
{!isOwnProfile && onAddFriend && (
|
||||
<div style={{ marginLeft: 'auto' }}>
|
||||
<Button
|
||||
variant={friendRequestSent ? 'secondary' : 'primary'}
|
||||
onClick={onAddFriend}
|
||||
disabled={friendRequestSent}
|
||||
size="sm"
|
||||
icon={<UserPlus size={16} />}
|
||||
>
|
||||
{friendRequestSent ? 'Request Sent' : 'Add Friend'}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ProfileHero>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Stack } from '@/ui/primitives/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { SectionHeader } from '@/ui/SectionHeader';
|
||||
import React from 'react';
|
||||
|
||||
interface ProfileSectionProps {
|
||||
@@ -14,19 +12,14 @@ interface ProfileSectionProps {
|
||||
|
||||
export function ProfileSection({ title, description, action, children }: ProfileSectionProps) {
|
||||
return (
|
||||
<Stack mb={8}>
|
||||
<Stack direction="row" align="center" justify="between" mb={4}>
|
||||
<Stack>
|
||||
<Heading level={2}>{title}</Heading>
|
||||
{description && (
|
||||
<Text color="text-gray-400" size="sm" mt={1} block>
|
||||
{description}
|
||||
</Text>
|
||||
)}
|
||||
</Stack>
|
||||
{action && <Stack>{action}</Stack>}
|
||||
</Stack>
|
||||
<Stack>{children}</Stack>
|
||||
</Stack>
|
||||
<section style={{ marginBottom: '2rem' }}>
|
||||
<SectionHeader
|
||||
title={title}
|
||||
description={description}
|
||||
actions={action}
|
||||
variant="minimal"
|
||||
/>
|
||||
<div>{children}</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Box } from '../../ui/primitives/Box';
|
||||
import { Grid } from '../../ui/primitives/Grid';
|
||||
import { Text } from '../../ui/Text';
|
||||
import { StatGrid } from '../../ui/StatGrid';
|
||||
import { Bug } from 'lucide-react';
|
||||
import React from 'react';
|
||||
|
||||
interface Stat {
|
||||
label: string;
|
||||
value: string | number;
|
||||
color?: string;
|
||||
intent?: 'primary' | 'telemetry' | 'success' | 'critical';
|
||||
}
|
||||
|
||||
interface ProfileStatGridProps {
|
||||
@@ -13,22 +13,18 @@ interface ProfileStatGridProps {
|
||||
}
|
||||
|
||||
export function ProfileStatGrid({ stats }: ProfileStatGridProps) {
|
||||
const mappedStats = stats.map(stat => ({
|
||||
label: stat.label,
|
||||
value: stat.value,
|
||||
intent: stat.intent || 'primary',
|
||||
icon: Bug // Default icon if none provided, but StatBox requires one
|
||||
}));
|
||||
|
||||
return (
|
||||
<Grid cols={2} mdCols={4} gap={4}>
|
||||
{stats.map((stat, idx) => (
|
||||
<Box
|
||||
key={idx}
|
||||
p={4}
|
||||
bg="bg-[#0f1115]"
|
||||
rounded="xl"
|
||||
border
|
||||
borderColor="border-[#262626]"
|
||||
textAlign="center"
|
||||
>
|
||||
<Text size="3xl" weight="bold" color={stat.color} block mb={1}>{stat.value}</Text>
|
||||
<Text size="xs" color="text-gray-500" uppercase letterSpacing="0.05em">{stat.label}</Text>
|
||||
</Box>
|
||||
))}
|
||||
</Grid>
|
||||
<StatGrid
|
||||
stats={mappedStats}
|
||||
columns={{ base: 2, md: 4 }}
|
||||
variant="box"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
'use client';
|
||||
|
||||
|
||||
import { Button } from '@/ui/Button';
|
||||
import { SegmentedControl } from '@/ui/SegmentedControl';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
import { Surface } from '@/ui/primitives/Surface';
|
||||
import { BarChart3, TrendingUp, User } from 'lucide-react';
|
||||
import React from 'react';
|
||||
|
||||
export type ProfileTab = 'overview' | 'stats' | 'ratings';
|
||||
|
||||
@@ -14,34 +13,17 @@ interface ProfileTabsProps {
|
||||
}
|
||||
|
||||
export function ProfileTabs({ activeTab, onTabChange }: ProfileTabsProps) {
|
||||
const options = [
|
||||
{ id: 'overview', label: 'Overview', icon: <Icon icon={User} size={4} /> },
|
||||
{ id: 'stats', label: 'Detailed Stats', icon: <Icon icon={BarChart3} size={4} /> },
|
||||
{ id: 'ratings', label: 'Ratings', icon: <Icon icon={TrendingUp} size={4} /> },
|
||||
];
|
||||
|
||||
return (
|
||||
<Surface variant="muted" rounded="xl" padding={1} style={{ backgroundColor: 'rgba(38, 38, 38, 0.5)', border: '1px solid #262626', width: 'fit-content' }}>
|
||||
<Box style={{ display: 'flex', alignItems: 'center', gap: '0.25rem' }}>
|
||||
<Button
|
||||
variant={activeTab === 'overview' ? 'primary' : 'ghost'}
|
||||
onClick={() => onTabChange('overview')}
|
||||
size="sm"
|
||||
icon={<Icon icon={User} size={4} />}
|
||||
>
|
||||
Overview
|
||||
</Button>
|
||||
<Button
|
||||
variant={activeTab === 'stats' ? 'primary' : 'ghost'}
|
||||
onClick={() => onTabChange('stats')}
|
||||
size="sm"
|
||||
icon={<Icon icon={BarChart3} size={4} />}
|
||||
>
|
||||
Detailed Stats
|
||||
</Button>
|
||||
<Button
|
||||
variant={activeTab === 'ratings' ? 'primary' : 'ghost'}
|
||||
onClick={() => onTabChange('ratings')}
|
||||
size="sm"
|
||||
icon={<Icon icon={TrendingUp} size={4} />}
|
||||
>
|
||||
Ratings
|
||||
</Button>
|
||||
</Box>
|
||||
</Surface>
|
||||
<SegmentedControl
|
||||
options={options}
|
||||
activeId={activeTab}
|
||||
onChange={(id) => onTabChange(id as ProfileTab)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ import { DriverViewModel as DriverViewModelClass } from '@/lib/view-models/Drive
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Image } from '@/ui/Image';
|
||||
import { Link } from '@/ui/Link';
|
||||
import { Box } from '@/ui/primitives/Box';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { UserDropdown, UserDropdownHeader, UserDropdownItem, UserDropdownFooter } from '@/ui/UserDropdown';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import {
|
||||
BarChart3,
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
Settings,
|
||||
Shield
|
||||
} from 'lucide-react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
// Hook to detect demo user mode based on session
|
||||
function useDemoUserMode(): { isDemo: boolean; demoRole: string | null } {
|
||||
@@ -143,32 +143,20 @@ export function UserPill() {
|
||||
// Handle unauthenticated users
|
||||
if (!session) {
|
||||
return (
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
|
||||
<Link
|
||||
href={routes.auth.login}
|
||||
variant="secondary"
|
||||
rounded="full"
|
||||
px={4}
|
||||
py={1.5}
|
||||
size="xs"
|
||||
hoverTextColor="text-white"
|
||||
hoverBorderColor="border-gray-500"
|
||||
>
|
||||
Sign In
|
||||
</Link>
|
||||
<Link
|
||||
href={routes.auth.signup}
|
||||
variant="primary"
|
||||
rounded="full"
|
||||
px={4}
|
||||
py={1.5}
|
||||
size="xs"
|
||||
shadow="0 0 12px rgba(25,140,255,0.5)"
|
||||
hoverBg="rgba(25,140,255,0.9)"
|
||||
>
|
||||
Get Started
|
||||
</Link>
|
||||
</Box>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -186,292 +174,123 @@ export function UserPill() {
|
||||
'super-admin': 'Super Admin',
|
||||
} as Record<string, string>)[demoRole || 'driver'] : null;
|
||||
|
||||
const roleColor = isDemo ? ({
|
||||
'driver': 'text-primary-blue',
|
||||
'sponsor': 'text-performance-green',
|
||||
'league-owner': 'text-purple-400',
|
||||
'league-steward': 'text-warning-amber',
|
||||
'league-admin': 'text-red-400',
|
||||
'system-owner': 'text-indigo-400',
|
||||
'super-admin': 'text-pink-400',
|
||||
} as Record<string, string>)[demoRole || 'driver'] : null;
|
||||
const roleIntent = isDemo ? ({
|
||||
'driver': 'primary',
|
||||
'sponsor': 'success',
|
||||
'league-owner': 'primary',
|
||||
'league-steward': 'warning',
|
||||
'league-admin': 'critical',
|
||||
'system-owner': 'primary',
|
||||
'super-admin': 'primary',
|
||||
} as Record<string, 'primary' | 'success' | 'warning' | 'critical'>)[demoRole || 'driver'] : 'low';
|
||||
|
||||
return (
|
||||
<Box position="relative" display="inline-flex" alignItems="center" data-user-pill>
|
||||
<Box
|
||||
as="button"
|
||||
<div style={{ position: 'relative', display: 'inline-flex', alignItems: 'center' }} data-user-pill>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsMenuOpen((open) => !open)}
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
gap={3}
|
||||
rounded="full"
|
||||
border
|
||||
px={3}
|
||||
py={1.5}
|
||||
transition
|
||||
cursor="pointer"
|
||||
bg="linear-gradient(to r, var(--iron-gray), var(--deep-graphite))"
|
||||
borderColor={isMenuOpen ? 'border-primary-blue/50' : 'border-charcoal-outline'}
|
||||
transform={isMenuOpen ? 'scale(1.02)' : 'scale(1)'}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '0.75rem',
|
||||
borderRadius: '9999px',
|
||||
border: `1px solid ${isMenuOpen ? 'var(--ui-color-intent-primary)' : 'var(--ui-color-border-default)'}`,
|
||||
padding: '0.375rem 0.75rem',
|
||||
background: 'var(--ui-color-bg-surface-muted)',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
{/* Avatar */}
|
||||
<Box position="relative">
|
||||
<div style={{ position: 'relative' }}>
|
||||
{avatarUrl ? (
|
||||
<Box w="8" h="8" rounded="full" overflow="hidden" bg="bg-charcoal-outline" display="flex" alignItems="center" justifyContent="center" border borderColor="border-charcoal-outline/80">
|
||||
<div style={{ width: '2rem', height: '2rem', borderRadius: '9999px', overflow: 'hidden', border: '1px solid var(--ui-color-border-default)' }}>
|
||||
<Image
|
||||
src={avatarUrl}
|
||||
alt={displayName}
|
||||
objectFit="cover"
|
||||
fill
|
||||
/>
|
||||
</Box>
|
||||
</div>
|
||||
) : (
|
||||
<Box w="8" h="8" rounded="full" bg="bg-primary-blue/20" border borderColor="border-primary-blue/30" display="flex" alignItems="center" justifyContent="center">
|
||||
<Text size="xs" weight="bold" color="text-primary-blue">
|
||||
<div style={{ width: '2rem', height: '2rem', borderRadius: '9999px', backgroundColor: 'var(--ui-color-intent-primary)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<Text size="xs" weight="bold" variant="high">
|
||||
{displayName[0]?.toUpperCase() || 'U'}
|
||||
</Text>
|
||||
</Box>
|
||||
</div>
|
||||
)}
|
||||
<Box position="absolute" bottom="-0.5" right="-0.5" w="3" h="3" rounded="full" bg="bg-primary-blue" border borderColor="border-deep-graphite" borderWidth="2px" />
|
||||
</Box>
|
||||
</div>
|
||||
|
||||
{/* Info */}
|
||||
<Box display={{ base: 'none', sm: 'flex' }} flexDirection="col" alignItems="start">
|
||||
<Text size="xs" weight="semibold" color="text-white" truncate maxWidth="100px" block>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'start' }}>
|
||||
<Text size="xs" weight="semibold" variant="high" truncate style={{ maxWidth: '100px' }}>
|
||||
{displayName}
|
||||
</Text>
|
||||
{roleLabel && (
|
||||
<Text size="xs" color={roleColor || 'text-gray-400'} weight="medium" fontSize="10px">
|
||||
<Text size="xs" variant={roleIntent as any} weight="medium" style={{ fontSize: '10px' }}>
|
||||
{roleLabel}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
</div>
|
||||
|
||||
{/* Chevron */}
|
||||
<Icon icon={ChevronDown} size={3.5} color="rgb(107, 114, 128)" groupHoverTextColor="text-gray-300" />
|
||||
</Box>
|
||||
<Icon icon={ChevronDown} size={3.5} intent="low" />
|
||||
</button>
|
||||
|
||||
<AnimatePresence>
|
||||
{isMenuOpen && (
|
||||
<Box
|
||||
as={motion.div}
|
||||
initial={{ opacity: 0, y: -10, scale: 0.95 }}
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
exit={{ opacity: 0, y: -10, scale: 0.95 }}
|
||||
transition={{ duration: 0.15 }}
|
||||
position="absolute"
|
||||
right={0}
|
||||
top="100%"
|
||||
mt={2}
|
||||
zIndex={50}
|
||||
>
|
||||
<Box w="56" rounded="xl" bg="bg-deep-graphite" border borderColor="border-charcoal-outline" shadow="xl" overflow="hidden">
|
||||
{/* Header */}
|
||||
<Box p={4} borderBottom borderColor="border-charcoal-outline" bg={`linear-gradient(to r, ${isDemo ? 'rgba(59, 130, 246, 0.1)' : 'rgba(38, 38, 38, 0.2)'}, transparent)`}>
|
||||
<Box display="flex" alignItems="center" gap={3}>
|
||||
{avatarUrl ? (
|
||||
<Box w="10" h="10" rounded="lg" overflow="hidden" bg="bg-charcoal-outline" display="flex" alignItems="center" justifyContent="center" border borderColor="border-charcoal-outline/80">
|
||||
<Image
|
||||
src={avatarUrl}
|
||||
alt={displayName}
|
||||
objectFit="cover"
|
||||
fill
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
<Box w="10" h="10" rounded="lg" bg="bg-primary-blue/20" border borderColor="border-primary-blue/30" display="flex" alignItems="center" justifyContent="center">
|
||||
<Text size="xs" weight="bold" color="text-primary-blue">
|
||||
{displayName[0]?.toUpperCase() || 'U'}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
<Box>
|
||||
<Text size="sm" weight="semibold" color="text-white" block>{displayName}</Text>
|
||||
{roleLabel && (
|
||||
<Text size="xs" color={roleColor || 'text-gray-400'} block>{roleLabel}</Text>
|
||||
)}
|
||||
{isDemo && (
|
||||
<Text size="xs" color="text-gray-500" block>Demo Account</Text>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
{isDemo && (
|
||||
<Text size="xs" color="text-gray-500" block mt={2}>
|
||||
Development account - not for production use
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
<UserDropdown isOpen={isMenuOpen}>
|
||||
<UserDropdownHeader variant={isDemo ? 'demo' : 'default'}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
|
||||
{avatarUrl ? (
|
||||
<div style={{ width: '2.5rem', height: '2.5rem', borderRadius: '0.5rem', overflow: 'hidden', border: '1px solid var(--ui-color-border-default)' }}>
|
||||
<Image
|
||||
src={avatarUrl}
|
||||
alt={displayName}
|
||||
objectFit="cover"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ width: '2.5rem', height: '2.5rem', borderRadius: '0.5rem', backgroundColor: 'var(--ui-color-intent-primary)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<Text size="xs" weight="bold" variant="high">
|
||||
{displayName[0]?.toUpperCase() || 'U'}
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<Text size="sm" weight="semibold" variant="high" block>{displayName}</Text>
|
||||
{roleLabel && (
|
||||
<Text size="xs" variant="low" block>{roleLabel}</Text>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</UserDropdownHeader>
|
||||
|
||||
{/* Menu Items */}
|
||||
<Box py={1}>
|
||||
{/* Admin link for Owner/Super Admin users */}
|
||||
{hasAdminAccess && (
|
||||
<Link
|
||||
href="/admin"
|
||||
block
|
||||
px={4}
|
||||
py={2.5}
|
||||
onClick={() => setIsMenuOpen(false)}
|
||||
>
|
||||
<Icon icon={Shield} size={4} color="rgb(129, 140, 248)" mr={3} />
|
||||
<Text size="sm" color="text-gray-200">Admin Area</Text>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{/* Sponsor portal link for demo sponsor users */}
|
||||
{isDemo && demoRole === 'sponsor' && (
|
||||
<>
|
||||
<Link
|
||||
href="/sponsor"
|
||||
block
|
||||
px={4}
|
||||
py={2.5}
|
||||
onClick={() => setIsMenuOpen(false)}
|
||||
>
|
||||
<Icon icon={BarChart3} size={4} color="rgb(16, 185, 129)" mr={3} />
|
||||
<Text size="sm" color="text-gray-200">Dashboard</Text>
|
||||
</Link>
|
||||
<Link
|
||||
href={routes.sponsor.campaigns}
|
||||
block
|
||||
px={4}
|
||||
py={2.5}
|
||||
onClick={() => setIsMenuOpen(false)}
|
||||
>
|
||||
<Icon icon={Megaphone} size={4} color="rgb(59, 130, 246)" mr={3} />
|
||||
<Text size="sm" color="text-gray-200">My Sponsorships</Text>
|
||||
</Link>
|
||||
<Link
|
||||
href={routes.sponsor.billing}
|
||||
block
|
||||
px={4}
|
||||
py={2.5}
|
||||
onClick={() => setIsMenuOpen(false)}
|
||||
>
|
||||
<Icon icon={CreditCard} size={4} color="rgb(245, 158, 11)" mr={3} />
|
||||
<Text size="sm" color="text-gray-200">Billing</Text>
|
||||
</Link>
|
||||
<Link
|
||||
href={routes.sponsor.settings}
|
||||
block
|
||||
px={4}
|
||||
py={2.5}
|
||||
onClick={() => setIsMenuOpen(false)}
|
||||
>
|
||||
<Icon icon={Settings} size={4} color="rgb(156, 163, 175)" mr={3} />
|
||||
<Text size="sm" color="text-gray-200">Settings</Text>
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
<div style={{ padding: '0.25rem 0' }}>
|
||||
{hasAdminAccess && (
|
||||
<UserDropdownItem href="/admin" icon={Shield} label="Admin Area" intent="primary" onClick={() => setIsMenuOpen(false)} />
|
||||
)}
|
||||
|
||||
{isDemo && demoRole === 'sponsor' && (
|
||||
<React.Fragment>
|
||||
<UserDropdownItem href="/sponsor" icon={BarChart3} label="Dashboard" onClick={() => setIsMenuOpen(false)} />
|
||||
<UserDropdownItem href={routes.sponsor.campaigns} icon={Megaphone} label="My Sponsorships" onClick={() => setIsMenuOpen(false)} />
|
||||
<UserDropdownItem href={routes.sponsor.billing} icon={CreditCard} label="Billing" onClick={() => setIsMenuOpen(false)} />
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
{/* Regular user profile links */}
|
||||
<Link
|
||||
href={routes.protected.profile}
|
||||
block
|
||||
px={4}
|
||||
py={2.5}
|
||||
onClick={() => setIsMenuOpen(false)}
|
||||
>
|
||||
<Text size="sm" color="text-gray-200">Profile</Text>
|
||||
</Link>
|
||||
<Link
|
||||
href={routes.protected.profileLeagues}
|
||||
block
|
||||
px={4}
|
||||
py={2.5}
|
||||
onClick={() => setIsMenuOpen(false)}
|
||||
>
|
||||
<Text size="sm" color="text-gray-200">Manage leagues</Text>
|
||||
</Link>
|
||||
<Link
|
||||
href={routes.protected.profileLiveries}
|
||||
block
|
||||
px={4}
|
||||
py={2.5}
|
||||
onClick={() => setIsMenuOpen(false)}
|
||||
>
|
||||
<Icon icon={Paintbrush} size={4} mr={2} />
|
||||
<Text size="sm" color="text-gray-200">Liveries</Text>
|
||||
</Link>
|
||||
<Link
|
||||
href={routes.protected.profileSponsorshipRequests}
|
||||
block
|
||||
px={4}
|
||||
py={2.5}
|
||||
onClick={() => setIsMenuOpen(false)}
|
||||
>
|
||||
<Icon icon={Handshake} size={4} color="rgb(16, 185, 129)" mr={2} />
|
||||
<Text size="sm" color="text-gray-200">Sponsorship Requests</Text>
|
||||
</Link>
|
||||
<Link
|
||||
href={routes.protected.profileSettings}
|
||||
block
|
||||
px={4}
|
||||
py={2.5}
|
||||
onClick={() => setIsMenuOpen(false)}
|
||||
>
|
||||
<Icon icon={Settings} size={4} mr={2} />
|
||||
<Text size="sm" color="text-gray-200">Settings</Text>
|
||||
</Link>
|
||||
<UserDropdownItem href={routes.protected.profile} label="Profile" onClick={() => setIsMenuOpen(false)} />
|
||||
<UserDropdownItem href={routes.protected.profileLeagues} label="Manage leagues" onClick={() => setIsMenuOpen(false)} />
|
||||
<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>
|
||||
|
||||
{/* Demo-specific info */}
|
||||
{isDemo && (
|
||||
<Box px={4} py={2} borderTop borderColor="border-charcoal-outline/50" mt={1}>
|
||||
<Text size="xs" color="text-gray-500" italic>Demo users have limited profile access</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Footer */}
|
||||
<Box borderTop borderColor="border-charcoal-outline">
|
||||
{isDemo ? (
|
||||
<Box
|
||||
as="button"
|
||||
type="button"
|
||||
onClick={handleLogout}
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="between"
|
||||
fullWidth
|
||||
px={4}
|
||||
py={3}
|
||||
cursor="pointer"
|
||||
transition
|
||||
bg="transparent"
|
||||
hoverBg="rgba(239, 68, 68, 0.05)"
|
||||
hoverColor="text-racing-red"
|
||||
>
|
||||
<Text size="sm" color="text-gray-500">Logout</Text>
|
||||
<Icon icon={LogOut} size={4} color="rgb(107, 114, 128)" />
|
||||
</Box>
|
||||
) : (
|
||||
<Box as="form" action="/auth/logout" method="POST">
|
||||
<Box
|
||||
as="button"
|
||||
type="submit"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="between"
|
||||
fullWidth
|
||||
px={4}
|
||||
py={3}
|
||||
cursor="pointer"
|
||||
transition
|
||||
bg="transparent"
|
||||
hoverBg="rgba(239, 68, 68, 0.1)"
|
||||
hoverColor="text-red-400"
|
||||
>
|
||||
<Text size="sm" color="text-gray-500">Logout</Text>
|
||||
<Icon icon={LogOut} size={4} color="rgb(107, 114, 128)" />
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</Box>
|
||||
<UserDropdownFooter>
|
||||
<UserDropdownItem
|
||||
icon={LogOut}
|
||||
label="Logout"
|
||||
intent="critical"
|
||||
onClick={isDemo ? handleLogout : undefined}
|
||||
/>
|
||||
</UserDropdownFooter>
|
||||
</UserDropdown>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user