website refactor
This commit is contained in:
@@ -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