diff --git a/apps/website/app/admin/page.tsx b/apps/website/app/admin/page.tsx index b563da58b..4c3344d2c 100644 --- a/apps/website/app/admin/page.tsx +++ b/apps/website/app/admin/page.tsx @@ -28,5 +28,5 @@ export default async function AdminPage() { const output = result.unwrap(); // Pass to client wrapper for UI interactions - return ; + return ; } \ No newline at end of file diff --git a/apps/website/app/admin/users/page.tsx b/apps/website/app/admin/users/page.tsx index 51d43d7d5..906e6eb97 100644 --- a/apps/website/app/admin/users/page.tsx +++ b/apps/website/app/admin/users/page.tsx @@ -30,5 +30,5 @@ export default async function AdminUsersPage() { const output = result.unwrap(); // Pass to client wrapper for UI interactions - return ; + return ; } \ No newline at end of file diff --git a/apps/website/app/leagues/[id]/stewarding/page.tsx b/apps/website/app/leagues/[id]/stewarding/page.tsx index afb2cdb5d..6e81f5225 100644 --- a/apps/website/app/leagues/[id]/stewarding/page.tsx +++ b/apps/website/app/leagues/[id]/stewarding/page.tsx @@ -25,7 +25,7 @@ export default async function LeagueStewardingPage({ params }: Props) { leagueId={leagueId} currentDriverId="" onRefetch={() => {}} - data={{ + viewData={{ leagueId, totalPending: 0, totalResolved: 0, @@ -39,7 +39,7 @@ export default async function LeagueStewardingPage({ params }: Props) { const data = result.unwrap(); return {}} // Should be handled diff --git a/apps/website/app/leagues/[id]/stewarding/protests/[protestId]/page.tsx b/apps/website/app/leagues/[id]/stewarding/protests/[protestId]/page.tsx index 5bcff0fbb..2fbacbafb 100644 --- a/apps/website/app/leagues/[id]/stewarding/protests/[protestId]/page.tsx +++ b/apps/website/app/leagues/[id]/stewarding/protests/[protestId]/page.tsx @@ -21,5 +21,5 @@ export default async function Page({ params }: Props) { const viewData = result.isOk() ? result.unwrap() : null; - return ; + return ; } diff --git a/apps/website/app/media/avatar/page.tsx b/apps/website/app/media/avatar/page.tsx index d8b55deed..21e8bbdc7 100644 --- a/apps/website/app/media/avatar/page.tsx +++ b/apps/website/app/media/avatar/page.tsx @@ -13,8 +13,12 @@ export default async function AvatarsPage() { return ( ); } diff --git a/apps/website/app/media/leagues/page.tsx b/apps/website/app/media/leagues/page.tsx index 45cc65875..20ff7e16b 100644 --- a/apps/website/app/media/leagues/page.tsx +++ b/apps/website/app/media/leagues/page.tsx @@ -13,8 +13,12 @@ export default async function LeaguesMediaPage() { return ( ); } diff --git a/apps/website/app/media/page.tsx b/apps/website/app/media/page.tsx index cfaea7032..1a8196ccd 100644 --- a/apps/website/app/media/page.tsx +++ b/apps/website/app/media/page.tsx @@ -23,8 +23,12 @@ export default async function MediaPage() { return ( ); } diff --git a/apps/website/app/media/sponsors/page.tsx b/apps/website/app/media/sponsors/page.tsx index 905a9e6dc..9741ea796 100644 --- a/apps/website/app/media/sponsors/page.tsx +++ b/apps/website/app/media/sponsors/page.tsx @@ -13,8 +13,12 @@ export default async function SponsorsMediaPage() { return ( ); } diff --git a/apps/website/app/media/teams/page.tsx b/apps/website/app/media/teams/page.tsx index 972c498e4..27ba87af7 100644 --- a/apps/website/app/media/teams/page.tsx +++ b/apps/website/app/media/teams/page.tsx @@ -13,8 +13,12 @@ export default async function TeamsMediaPage() { return ( ); } diff --git a/apps/website/app/media/tracks/page.tsx b/apps/website/app/media/tracks/page.tsx index ee8d10ce6..c77777de5 100644 --- a/apps/website/app/media/tracks/page.tsx +++ b/apps/website/app/media/tracks/page.tsx @@ -13,8 +13,12 @@ export default async function TracksMediaPage() { return ( ); } diff --git a/apps/website/app/races/[id]/stewarding/page.tsx b/apps/website/app/races/[id]/stewarding/page.tsx index f0b8ed925..04a57d28e 100644 --- a/apps/website/app/races/[id]/stewarding/page.tsx +++ b/apps/website/app/races/[id]/stewarding/page.tsx @@ -68,9 +68,9 @@ export default function RaceStewardingPage({ params }: RaceStewardingPageProps) isLoading={isLoading} error={error} retry={fetchData} - Template={({ data }) => ( + Template={({ viewData }) => ( ; + return ; } diff --git a/apps/website/app/sponsor/signup/page.tsx b/apps/website/app/sponsor/signup/page.tsx index 28237d635..01fa2877b 100644 --- a/apps/website/app/sponsor/signup/page.tsx +++ b/apps/website/app/sponsor/signup/page.tsx @@ -259,7 +259,7 @@ export default function SponsorSignupPage() { {/* Platform Stats */} - + {PLATFORM_STATS.map((stat, index) => ( ; + return ; } diff --git a/apps/website/build_output.txt b/apps/website/build_output.txt new file mode 100644 index 000000000..c9b1f76bc --- /dev/null +++ b/apps/website/build_output.txt @@ -0,0 +1,30 @@ + +> @gridpilot/website@0.1.0 build +> next build + + ▲ Next.js 15.5.9 + - Environments: .env.local + + Creating an optimized production build ... + ✓ Compiled successfully in 3.7s + Skipping linting + Checking validity of types ... +Failed to compile. + +./hooks/league/useCreateLeague.ts:7:17 +Type error: Return type of exported function has or is using name 'CreateLeagueInput' from external module "/Users/marcmintel/Projects/gridpilot/apps/website/hooks/league/useCreateLeagueWithBlockers" but cannot be named. + +  5 |  * This wrapper maintains backward compatibility while using the new blocker-aware hook +  6 |  */ +> 7 | export function useCreateLeague() { +  | ^ +  8 | return useCreateLeagueWithBlockers(); +  9 | } +Next.js build worker exited with code: 1 and signal: null +npm error Lifecycle script `build` failed with error: +npm error code 1 +npm error path /Users/marcmintel/Projects/gridpilot/apps/website +npm error workspace @gridpilot/website@0.1.0 +npm error location /Users/marcmintel/Projects/gridpilot/apps/website +npm error command failed +npm error command sh -c next build diff --git a/apps/website/client-wrapper/LeagueAdminSchedulePageClient.tsx b/apps/website/client-wrapper/LeagueAdminSchedulePageClient.tsx index d9fe55161..1e78d192b 100644 --- a/apps/website/client-wrapper/LeagueAdminSchedulePageClient.tsx +++ b/apps/website/client-wrapper/LeagueAdminSchedulePageClient.tsx @@ -175,7 +175,7 @@ export function LeagueAdminSchedulePageClient() { - Admin Access Required + Admin Access Required Only league admins can manage the schedule. @@ -190,10 +190,10 @@ export function LeagueAdminSchedulePageClient() { data={templateData} isLoading={isLoading} error={null} - Template={({ data }) => ( + Template={({ viewData }) => ( <> ) { +export function SponsorLeagueDetailPageClient({ viewData }: ClientWrapperProps) { const [activeTab, setActiveTab] = useState<'overview' | 'drivers' | 'races' | 'sponsor'>('overview'); const [selectedTier, setSelectedTier] = useState<'main' | 'secondary'>('main'); diff --git a/apps/website/components/dashboard/DashboardHero.tsx b/apps/website/components/dashboard/DashboardHero.tsx index a596cfdf3..322eba140 100644 --- a/apps/website/components/dashboard/DashboardHero.tsx +++ b/apps/website/components/dashboard/DashboardHero.tsx @@ -1,11 +1,13 @@ import { Flag, Star, Trophy, Users } from 'lucide-react'; import { Avatar } from '../../ui/Avatar'; import { Badge } from '../../ui/Badge'; +import { Box } from '../../ui/Box'; import { Heading } from '../../ui/Heading'; import { Icon } from '../../ui/Icon'; import { ProfileHero, ProfileAvatar, ProfileStatsGroup, ProfileStat } from '../../ui/ProfileHero'; import { BadgeGroup } from '../../ui/BadgeGroup'; import { QuickStatCard, QuickStatItem } from '../../ui/QuickStatCard'; +import { Stack } from '../../ui/Stack'; import React from 'react'; interface DashboardHeroProps { @@ -27,7 +29,7 @@ export function DashboardHero({ }: DashboardHeroProps) { return ( -
+ {/* Avatar Section */} } @@ -40,7 +42,7 @@ export function DashboardHero({ {/* Info Section */} -
+ {driverName} @@ -60,14 +62,14 @@ export function DashboardHero({ Team Redline -
+ {/* Quick Stats */} -
+
); } diff --git a/apps/website/components/dashboard/DashboardHeroWrapper.tsx b/apps/website/components/dashboard/DashboardHeroWrapper.tsx index bf1b66c0a..096896c57 100644 --- a/apps/website/components/dashboard/DashboardHeroWrapper.tsx +++ b/apps/website/components/dashboard/DashboardHeroWrapper.tsx @@ -5,6 +5,7 @@ import { routes } from '@/lib/routing/RouteConfig'; import { Button } from '@/ui/Button'; import { Icon } from '@/ui/Icon'; import { Link } from '@/ui/Link'; +import { Stack } from '@/ui/Stack'; import { StatGrid } from '@/ui/StatGrid'; import { Flag, Medal, Target, Trophy, User, Users } from 'lucide-react'; import React from 'react'; @@ -26,7 +27,7 @@ interface DashboardHeroProps { export function DashboardHero({ currentDriver, activeLeaguesCount }: DashboardHeroProps) { return ( -
+ -
+ -
+
-
+ ); } diff --git a/apps/website/components/dev/DevToolbar.tsx b/apps/website/components/dev/DevToolbar.tsx index 173df45fc..4f21c48c0 100644 --- a/apps/website/components/dev/DevToolbar.tsx +++ b/apps/website/components/dev/DevToolbar.tsx @@ -4,7 +4,7 @@ import { useNotifications } from '@/components/notifications/NotificationProvide import type { NotificationVariant } from '@/components/notifications/notificationTypes'; import { useEffectiveDriverId } from "@/hooks/useEffectiveDriverId"; import { ApiConnectionMonitor } from '@/lib/api/base/ApiConnectionMonitor'; -import { CircuitBreakerRegistry } from '@/lib/api/base/CircuitBreakerRegistry'; +import { CircuitBreakerRegistry } from '@/lib/api/base/RetryHandler'; import { getGlobalErrorHandler } from '@/lib/infrastructure/GlobalErrorHandler'; import { Activity, AlertTriangle, ChevronDown, ChevronUp, MessageSquare, Wrench, X } from 'lucide-react'; import { useEffect, useState } from 'react'; diff --git a/apps/website/components/dev/sections/ReplaySection.tsx b/apps/website/components/dev/sections/ReplaySection.tsx index 87e566a1b..9614faf5b 100644 --- a/apps/website/components/dev/sections/ReplaySection.tsx +++ b/apps/website/components/dev/sections/ReplaySection.tsx @@ -2,11 +2,12 @@ import { getGlobalReplaySystem } from '@/lib/infrastructure/ErrorReplay'; import { Button } from '@/ui/Button'; +import { Box } from '@/ui/Box'; import { Icon } from '@/ui/Icon'; import { IconButton } from '@/ui/IconButton'; import { Stack } from '@/ui/Stack'; import { Text } from '@/ui/Text'; -import { Box, Clock, Copy, Download, Play, Trash2 } from 'lucide-react'; +import { Clock, Copy, Download, Play, Trash2 } from 'lucide-react'; import { useEffect, useState } from 'react'; interface ReplayEntry { diff --git a/apps/website/components/drivers/ProfileRaceHistory.tsx b/apps/website/components/drivers/ProfileRaceHistory.tsx index 6ad5d719c..a3df78834 100644 --- a/apps/website/components/drivers/ProfileRaceHistory.tsx +++ b/apps/website/components/drivers/ProfileRaceHistory.tsx @@ -11,8 +11,7 @@ import { Group } from '@/ui/Group'; import { Stack } from '@/ui/Stack'; import { ControlBar } from '@/ui/ControlBar'; import { Trophy } from 'lucide-react'; -import { useEffect, useState } from 'react'; -import React from 'react'; +import React, { useEffect, useState } from 'react'; interface RaceHistoryProps { driverId: string; diff --git a/apps/website/components/drivers/RatingBadge.tsx b/apps/website/components/drivers/RatingBadge.tsx index 561ff3dac..9d09e3252 100644 --- a/apps/website/components/drivers/RatingBadge.tsx +++ b/apps/website/components/drivers/RatingBadge.tsx @@ -3,10 +3,12 @@ import { Badge } from '@/ui/Badge'; interface RatingBadgeProps { rating: number; - size?: 'sm' | 'md'; + size?: 'sm' | 'md' | 'lg'; } export function RatingBadge({ rating, size = 'md' }: RatingBadgeProps) { + const badgeSize = size === 'lg' ? 'md' : size; + const getVariant = (val: number): 'warning' | 'primary' | 'success' | 'default' => { if (val >= 2500) return 'warning'; if (val >= 2000) return 'primary'; // Simplified @@ -18,7 +20,7 @@ export function RatingBadge({ rating, size = 'md' }: RatingBadgeProps) { return ( {rating.toLocaleString()} diff --git a/apps/website/components/drivers/SafetyRatingBadge.tsx b/apps/website/components/drivers/SafetyRatingBadge.tsx index 44dabc9a5..f701a717d 100644 --- a/apps/website/components/drivers/SafetyRatingBadge.tsx +++ b/apps/website/components/drivers/SafetyRatingBadge.tsx @@ -56,7 +56,7 @@ export function SafetyRatingBadge({ rating, size = 'md' }: SafetyRatingBadgeProp border bg={getBgColor(rating)} borderColor={getBorderColor(rating)} - {...sizeProps[size]} + {...(sizeProps[size] as any)} > + {children} - + ); } diff --git a/apps/website/components/feed/FeedList.tsx b/apps/website/components/feed/FeedList.tsx index 2f163cb23..a2467daaa 100644 --- a/apps/website/components/feed/FeedList.tsx +++ b/apps/website/components/feed/FeedList.tsx @@ -1,5 +1,6 @@ import { FeedItemCard } from '@/components/feed/FeedItemCard'; import { FeedEmptyState } from '@/ui/FeedEmptyState'; +import { Stack } from '@/ui/Stack'; import React from 'react'; interface FeedItemData { @@ -23,10 +24,10 @@ export function FeedList({ items }: FeedListProps) { } return ( -
+ {items.map(item => ( ))} -
+ ); } diff --git a/apps/website/components/layout/AuthedNav.tsx b/apps/website/components/layout/AuthedNav.tsx index 78b700c0f..2771ac9f5 100644 --- a/apps/website/components/layout/AuthedNav.tsx +++ b/apps/website/components/layout/AuthedNav.tsx @@ -1,7 +1,7 @@ import { routes } from '@/lib/routing/RouteConfig'; import { Stack } from '@/ui/Stack'; import { Calendar, Home, Layout, Settings, Trophy, Users } from 'lucide-react'; -import { NavLink } from './NavLink'; +import { NavLink } from '@/ui/NavLink'; interface AuthedNavProps { pathname: string; diff --git a/apps/website/components/layout/PublicNav.tsx b/apps/website/components/layout/PublicNav.tsx index 3fe644905..f87aee5df 100644 --- a/apps/website/components/layout/PublicNav.tsx +++ b/apps/website/components/layout/PublicNav.tsx @@ -1,7 +1,7 @@ import { routes } from '@/lib/routing/RouteConfig'; import { Stack } from '@/ui/Stack'; import { Calendar, Home, Layout, Trophy, Users } from 'lucide-react'; -import { NavLink } from './NavLink'; +import { NavLink } from '@/ui/NavLink'; interface PublicNavProps { pathname: string; diff --git a/apps/website/components/leaderboards/RankBadge.tsx b/apps/website/components/leaderboards/RankBadge.tsx index 7d6b2b946..1a01c61bd 100644 --- a/apps/website/components/leaderboards/RankBadge.tsx +++ b/apps/website/components/leaderboards/RankBadge.tsx @@ -5,10 +5,12 @@ import React from 'react'; interface RankBadgeProps { rank: number; - size?: 'sm' | 'md'; + size?: 'sm' | 'md' | 'lg'; } export function RankBadge({ rank, size = 'md' }: RankBadgeProps) { + const badgeSize = size === 'lg' ? 'md' : size; + const getVariant = (rank: number): 'warning' | 'primary' | 'info' | 'default' => { if (rank <= 3) return 'warning'; if (rank <= 10) return 'primary'; @@ -28,7 +30,7 @@ export function RankBadge({ rank, size = 'md' }: RankBadgeProps) { const medal = getMedalEmoji(rank); return ( - + {medal && {medal}} #{rank} diff --git a/apps/website/components/leagues/LeagueMemberRow.tsx b/apps/website/components/leagues/LeagueMemberRow.tsx index 89ac9c601..0b7bc9aa4 100644 --- a/apps/website/components/leagues/LeagueMemberRow.tsx +++ b/apps/website/components/leagues/LeagueMemberRow.tsx @@ -1,6 +1,7 @@ import { DriverIdentity } from '@/ui/DriverIdentity'; import { DriverViewModel } from '@/lib/view-models/DriverViewModel'; import { Badge } from '@/ui/Badge'; +import { Box } from '@/ui/Box'; import { TableCell, TableRow } from '@/ui/Table'; import { Text } from '@/ui/Text'; import { DateDisplay } from '@/lib/display-objects/DateDisplay'; @@ -41,7 +42,7 @@ export function LeagueMemberRow({ return ( -
+ {driver ? ( ⭐ )} -
+
diff --git a/apps/website/components/leagues/LeagueMembers.tsx b/apps/website/components/leagues/LeagueMembers.tsx index 7d1a08179..467b597b7 100644 --- a/apps/website/components/leagues/LeagueMembers.tsx +++ b/apps/website/components/leagues/LeagueMembers.tsx @@ -17,8 +17,7 @@ import { Text } from '@/ui/Text'; import { Box } from '@/ui/Box'; import { Group } from '@/ui/Group'; import { ControlBar } from '@/ui/ControlBar'; -import { useCallback, useEffect, useState } from 'react'; -import React from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; interface LeagueMembersProps { leagueId: string; diff --git a/apps/website/components/leagues/LeagueSchedule.tsx b/apps/website/components/leagues/LeagueSchedule.tsx index b0007b6c8..a38227834 100644 --- a/apps/website/components/leagues/LeagueSchedule.tsx +++ b/apps/website/components/leagues/LeagueSchedule.tsx @@ -7,7 +7,7 @@ import type { LeagueScheduleRaceViewModel } from '@/lib/view-models/LeagueSchedu import { useState } from 'react'; // Shared state components -import { StateContainer } from '@/ui/StateContainer'; +import { StateContainer } from '@/components/shared/state/StateContainer'; import { useLeagueSchedule } from "@/hooks/league/useLeagueSchedule"; import { Button } from '@/ui/Button'; import { Heading } from '@/ui/Heading'; diff --git a/apps/website/components/media/MediaGallery.tsx b/apps/website/components/media/MediaGallery.tsx index f188c4838..5e41f614f 100644 --- a/apps/website/components/media/MediaGallery.tsx +++ b/apps/website/components/media/MediaGallery.tsx @@ -1,7 +1,6 @@ 'use client'; import { Text } from '@/ui/Text'; -import { useState } from 'react'; import { MediaCard } from '@/ui/MediaCard'; import { MediaFiltersBar } from './MediaFiltersBar'; import { Grid } from '@/ui/Grid'; @@ -10,7 +9,7 @@ import { MediaViewerModal } from './MediaViewerModal'; import { SectionHeader } from '@/ui/SectionHeader'; import { EmptyState } from '@/ui/EmptyState'; import { Search } from 'lucide-react'; -import React from 'react'; +import React, { useState } from 'react'; export interface MediaAsset { id: string; diff --git a/apps/website/components/mockups/DriverProfileMockup.tsx b/apps/website/components/mockups/DriverProfileMockup.tsx index 5d42ff02f..a1c9f9046 100644 --- a/apps/website/components/mockups/DriverProfileMockup.tsx +++ b/apps/website/components/mockups/DriverProfileMockup.tsx @@ -235,7 +235,7 @@ function AnimatedRating({ shouldReduceMotion, value }: { shouldReduceMotion: boo return ( - {shouldReduceMotion ? value : {rounded}} + {shouldReduceMotion ? value : {rounded}} ); } @@ -265,7 +265,7 @@ function AnimatedCounter({ return ( - {shouldReduceMotion ? value : {rounded}}{suffix} + {shouldReduceMotion ? value : {rounded}}{suffix} ); } diff --git a/apps/website/components/mockups/StandingsTableMockup.tsx b/apps/website/components/mockups/StandingsTableMockup.tsx index 432ba270c..2d67d5c95 100644 --- a/apps/website/components/mockups/StandingsTableMockup.tsx +++ b/apps/website/components/mockups/StandingsTableMockup.tsx @@ -270,7 +270,7 @@ function AnimatedPoints({ weight="bold" color="text-white" > - {shouldReduceMotion ? points : {spring}} + {shouldReduceMotion ? points : {spring}} diff --git a/apps/website/components/profile/ProfileStatGrid.tsx b/apps/website/components/profile/ProfileStatGrid.tsx index ff374bb56..ecf1a8eaa 100644 --- a/apps/website/components/profile/ProfileStatGrid.tsx +++ b/apps/website/components/profile/ProfileStatGrid.tsx @@ -6,6 +6,7 @@ interface Stat { label: string; value: string | number; intent?: 'primary' | 'telemetry' | 'success' | 'critical'; + color?: string; } interface ProfileStatGridProps { @@ -17,6 +18,7 @@ export function ProfileStatGrid({ stats }: ProfileStatGridProps) { label: stat.label, value: stat.value, intent: stat.intent || 'primary', + color: stat.color, icon: Bug // Default icon if none provided, but StatBox requires one })); diff --git a/apps/website/components/profile/UserPill.tsx b/apps/website/components/profile/UserPill.tsx index e89b9d0f8..f0abcbfa7 100644 --- a/apps/website/components/profile/UserPill.tsx +++ b/apps/website/components/profile/UserPill.tsx @@ -190,7 +190,8 @@ export function UserPill() { return ( - + diff --git a/apps/website/components/races/LatestResultsSidebar.tsx b/apps/website/components/races/LatestResultsSidebar.tsx index a94a8b015..a9900d4d5 100644 --- a/apps/website/components/races/LatestResultsSidebar.tsx +++ b/apps/website/components/races/LatestResultsSidebar.tsx @@ -35,7 +35,7 @@ export function LatestResultsSidebar({ results }: LatestResultsSidebarProps) {
); diff --git a/apps/website/components/races/RaceListItemWrapper.tsx b/apps/website/components/races/RaceListItemWrapper.tsx index 569d8f92b..53331073f 100644 --- a/apps/website/components/races/RaceListItemWrapper.tsx +++ b/apps/website/components/races/RaceListItemWrapper.tsx @@ -24,22 +24,22 @@ interface RaceListItemProps { export function RaceListItem({ race, onClick }: RaceListItemProps) { const statusConfig = { scheduled: { - icon: Clock, + iconName: 'Clock', variant: 'primary' as const, label: 'Scheduled', }, running: { - icon: PlayCircle, + iconName: 'PlayCircle', variant: 'success' as const, label: 'LIVE', }, completed: { - icon: CheckCircle2, + iconName: 'CheckCircle2', variant: 'default' as const, label: 'Completed', }, cancelled: { - icon: XCircle, + iconName: 'XCircle', variant: 'warning' as const, label: 'Cancelled', }, @@ -64,11 +64,13 @@ export function RaceListItem({ race, onClick }: RaceListItemProps) { dayLabel={date.getDate().toString()} timeLabel={formatTime(race.scheduledAt)} status={race.status} + statusLabel={config.label} + statusVariant={config.variant} + statusIconName={config.iconName} leagueName={race.leagueName} leagueHref={race.leagueId ? routes.league.detail(race.leagueId) : undefined} strengthOfField={race.strengthOfField} onClick={() => onClick(race.id)} - statusConfig={config} /> ); } diff --git a/apps/website/components/races/SessionSummaryPanel.tsx b/apps/website/components/races/SessionSummaryPanel.tsx index 78a864918..4a8254a6d 100644 --- a/apps/website/components/races/SessionSummaryPanel.tsx +++ b/apps/website/components/races/SessionSummaryPanel.tsx @@ -1,5 +1,6 @@ import { Box } from '@/ui/Box'; import { Panel } from '@/ui/Panel'; +import { Stack } from '@/ui/Stack'; import { StatusDot } from '@/ui/StatusDot'; import { Text } from '@/ui/Text'; @@ -30,16 +31,16 @@ export function SessionSummaryPanel({ return ( - - + + {title} - + {status} - - + + {startTime && ( @@ -61,7 +62,7 @@ export function SessionSummaryPanel({ )} -
+ ); } diff --git a/apps/website/components/races/TelemetryStrip.tsx b/apps/website/components/races/TelemetryStrip.tsx index 6b4e1aba0..99cbd8fb7 100644 --- a/apps/website/components/races/TelemetryStrip.tsx +++ b/apps/website/components/races/TelemetryStrip.tsx @@ -1,4 +1,5 @@ import { Box } from '@/ui/Box'; +import { Stack } from '@/ui/Stack'; import { Text } from '@/ui/Text'; interface TelemetryItem { @@ -25,7 +26,7 @@ export function TelemetryStrip({ items, className = '' }: TelemetryStripProps) { className={`bg-graphite-black/80 border-y border-border-gray/30 py-2 px-4 flex items-center gap-8 overflow-x-auto no-scrollbar ${className}`} > {items.map((item, index) => ( - + {item.label} @@ -42,7 +43,7 @@ export function TelemetryStrip({ items, className = '' }: TelemetryStripProps) { {item.trend === 'up' ? '↑' : item.trend === 'down' ? '↓' : '•'} )} - + ))} ); diff --git a/apps/website/components/races/TrackConditionsPanel.tsx b/apps/website/components/races/TrackConditionsPanel.tsx index ee6e4d8ff..a81ba880e 100644 --- a/apps/website/components/races/TrackConditionsPanel.tsx +++ b/apps/website/components/races/TrackConditionsPanel.tsx @@ -3,6 +3,7 @@ import { Icon } from '@/ui/Icon'; import { Text } from '@/ui/Text'; import { Stack } from '@/ui/Stack'; +import { Grid } from '@/ui/Grid'; import { Droplets, Sun, Thermometer, Wind, type LucideIcon } from 'lucide-react'; interface TrackConditionsPanelProps { @@ -26,7 +27,7 @@ export function TrackConditionsPanel({ Track Conditions - + - + diff --git a/apps/website/components/races/UpcomingRacesSidebar.tsx b/apps/website/components/races/UpcomingRacesSidebar.tsx index 75e3c4df4..9004248dd 100644 --- a/apps/website/components/races/UpcomingRacesSidebar.tsx +++ b/apps/website/components/races/UpcomingRacesSidebar.tsx @@ -42,7 +42,7 @@ export function UpcomingRacesSidebar({ races }: UpcomingRacesSidebarProps) { key={race.id} track={race.track} meta={race.car} - date={scheduledAt} + dateLabel={scheduledAt.toLocaleDateString()} /> ); })} diff --git a/apps/website/components/shared/RangeField.tsx b/apps/website/components/shared/RangeField.tsx index d1ec47b4a..7d4b76d52 100644 --- a/apps/website/components/shared/RangeField.tsx +++ b/apps/website/components/shared/RangeField.tsx @@ -130,7 +130,8 @@ export function RangeField({ {label} -
-
+
{clampedValue} @@ -202,7 +203,8 @@ export function RangeField({ )} {/* Custom slider */} -
-
+
{/* Value input and quick presets */} - 0 && ( {quickPresets.slice(0, 3).map((preset) => ( - + ))} )} diff --git a/apps/website/components/shared/UIComponents.tsx b/apps/website/components/shared/UIComponents.tsx index 73d3cfa14..816f0203e 100644 --- a/apps/website/components/shared/UIComponents.tsx +++ b/apps/website/components/shared/UIComponents.tsx @@ -18,6 +18,7 @@ import { LoadingSpinner } from '@/ui/LoadingSpinner'; import { Badge } from '@/ui/Badge'; import { ProgressLine } from '@/ui/ProgressLine'; +import { SharedEmptyState } from './SharedEmptyState'; export { Pagination as SharedPagination, @@ -38,5 +39,6 @@ export { Skeleton as SharedSkeleton, LoadingSpinner as SharedLoadingSpinner, Badge as SharedBadge, - ProgressLine as SharedProgressLine + ProgressLine as SharedProgressLine, + SharedEmptyState }; diff --git a/apps/website/components/sponsors/TransactionTable.tsx b/apps/website/components/sponsors/TransactionTable.tsx index d69824d33..adab9a24a 100644 --- a/apps/website/components/sponsors/TransactionTable.tsx +++ b/apps/website/components/sponsors/TransactionTable.tsx @@ -84,8 +84,7 @@ export function TransactionTable({ transactions, onDownload }: TransactionTableP return ( + - + ); } diff --git a/apps/website/components/teams/TeamAdmin.tsx b/apps/website/components/teams/TeamAdmin.tsx index 2f0b9720b..729a85e8d 100644 --- a/apps/website/components/teams/TeamAdmin.tsx +++ b/apps/website/components/teams/TeamAdmin.tsx @@ -19,8 +19,7 @@ import { SectionHeader } from '@/ui/SectionHeader'; import { Box } from '@/ui/Box'; import { Group } from '@/ui/Group'; import { Stack } from '@/ui/Stack'; -import { useState } from 'react'; -import React from 'react'; +import React, { useState } from 'react'; interface TeamAdminProps { team: { diff --git a/apps/website/hooks/auth/useForgotPassword.ts b/apps/website/hooks/auth/useForgotPassword.ts index aa25d2d64..246fabe17 100644 --- a/apps/website/hooks/auth/useForgotPassword.ts +++ b/apps/website/hooks/auth/useForgotPassword.ts @@ -10,7 +10,13 @@ export function useForgotPassword( const authService = useInject(AUTH_SERVICE_TOKEN); return useMutation<{ message: string; magicLink?: string }, ApiError, ForgotPasswordDTO>({ - mutationFn: (params) => authService.forgotPassword(params), + mutationFn: async (params) => { + const result = await authService.forgotPassword(params); + if (result.isErr()) { + throw result.getError(); + } + return result.unwrap(); + }, ...options, }); } \ No newline at end of file diff --git a/apps/website/hooks/auth/useLogin.ts b/apps/website/hooks/auth/useLogin.ts index 485979728..88db4bc73 100644 --- a/apps/website/hooks/auth/useLogin.ts +++ b/apps/website/hooks/auth/useLogin.ts @@ -11,7 +11,13 @@ export function useLogin( const authService = useInject(AUTH_SERVICE_TOKEN); return useMutation({ - mutationFn: (params) => authService.login(params), + mutationFn: async (params) => { + const result = await authService.login(params); + if (result.isErr()) { + throw result.getError(); + } + return result.unwrap(); + }, ...options, }); } \ No newline at end of file diff --git a/apps/website/hooks/auth/useResetPassword.ts b/apps/website/hooks/auth/useResetPassword.ts index 617b95c0f..b406a4f06 100644 --- a/apps/website/hooks/auth/useResetPassword.ts +++ b/apps/website/hooks/auth/useResetPassword.ts @@ -10,7 +10,13 @@ export function useResetPassword( const authService = useInject(AUTH_SERVICE_TOKEN); return useMutation<{ message: string }, ApiError, ResetPasswordDTO>({ - mutationFn: (params) => authService.resetPassword(params), + mutationFn: async (params) => { + const result = await authService.resetPassword(params); + if (result.isErr()) { + throw result.getError(); + } + return result.unwrap(); + }, ...options, }); } \ No newline at end of file diff --git a/apps/website/hooks/auth/useSignup.ts b/apps/website/hooks/auth/useSignup.ts index 9b4e8ec91..18c0f26fc 100644 --- a/apps/website/hooks/auth/useSignup.ts +++ b/apps/website/hooks/auth/useSignup.ts @@ -11,7 +11,13 @@ export function useSignup( const authService = useInject(AUTH_SERVICE_TOKEN); return useMutation({ - mutationFn: (params) => authService.signup(params), + mutationFn: async (params) => { + const result = await authService.signup(params); + if (result.isErr()) { + throw result.getError(); + } + return result.unwrap(); + }, ...options, }); } \ No newline at end of file diff --git a/apps/website/hooks/driver/useDriverProfilePageData.ts b/apps/website/hooks/driver/useDriverProfilePageData.ts index af22d8abb..b6769f2e3 100644 --- a/apps/website/hooks/driver/useDriverProfilePageData.ts +++ b/apps/website/hooks/driver/useDriverProfilePageData.ts @@ -17,7 +17,10 @@ export function useDriverProfilePageData(driverId: string) { queryFn: async () => { if (!driverId) return []; - const allTeams = await teamService.getAllTeams(); + const allTeamsResult = await teamService.getAllTeams(); + if (allTeamsResult.isErr()) return []; + const allTeams = allTeamsResult.unwrap(); + let teamMemberships: Array<{ team: { id: string; name: string }; role: string; @@ -25,7 +28,10 @@ export function useDriverProfilePageData(driverId: string) { }> = []; for (const team of allTeams) { - const teamMembers = await teamService.getTeamMembers(team.id, driverId, ''); + const teamMembersResult = await teamService.getTeamMembers(team.id, driverId, ''); + if (teamMembersResult.isErr()) continue; + const teamMembers = teamMembersResult.unwrap(); + const membership = teamMembers?.find(member => member.driverId === driverId); if (membership) { teamMemberships.push({ diff --git a/apps/website/hooks/league/useCreateLeagueWithBlockers.ts b/apps/website/hooks/league/useCreateLeagueWithBlockers.ts index 6fe30983e..3cdd5d055 100644 --- a/apps/website/hooks/league/useCreateLeagueWithBlockers.ts +++ b/apps/website/hooks/league/useCreateLeagueWithBlockers.ts @@ -5,14 +5,14 @@ import { ApiError } from '@/lib/api/base/ApiError'; import { CreateLeagueInputDTO } from '@/lib/types/generated/CreateLeagueInputDTO'; import { CreateLeagueOutputDTO } from '@/lib/types/generated/CreateLeagueOutputDTO'; -interface CreateLeagueInput { +export interface CreateLeagueInput { name: string; description: string; maxDrivers: number; scoringPresetId: string; } -interface CreateLeagueResult { +export interface CreateLeagueResult { success: boolean; leagueId: string; error?: string; diff --git a/apps/website/hooks/league/useLeagueDetail.ts b/apps/website/hooks/league/useLeagueDetail.ts index 8afcc6a4d..8f31ccb8d 100644 --- a/apps/website/hooks/league/useLeagueDetail.ts +++ b/apps/website/hooks/league/useLeagueDetail.ts @@ -22,9 +22,13 @@ export function useLeagueDetail({ leagueId, queryOptions }: UseLeagueDetailOptio return useQuery({ queryKey: ['league-detail', leagueId], queryFn: async () => { - const result = await leagueService.getAllLeagues() as AllLeaguesWithCapacityAndScoringDTO; + const result = await leagueService.getAllLeagues(); + if (result.isErr()) { + throw result.getError(); + } + const data = result.unwrap(); // Filter for the specific league - const leagues = Array.isArray(result?.leagues) ? result.leagues : []; + const leagues = Array.isArray(data?.leagues) ? data.leagues : []; const league = leagues.find(l => l.id === leagueId); if (!league) { throw new ApiError('League not found', 'NOT_FOUND', { @@ -46,8 +50,11 @@ export function useLeagueMemberships({ leagueId, queryOptions }: UseLeagueMember queryKey: ['league-memberships', leagueId], queryFn: async () => { const result = await leagueService.getLeagueMemberships(leagueId); + if (result.isErr()) { + throw result.getError(); + } // The DTO already has the correct structure with members property - return result; + return result.unwrap(); }, ...queryOptions, }); diff --git a/apps/website/hooks/league/useLeagueSponsorshipsPageData.ts b/apps/website/hooks/league/useLeagueSponsorshipsPageData.ts index ef6162faf..0eda68f6a 100644 --- a/apps/website/hooks/league/useLeagueSponsorshipsPageData.ts +++ b/apps/website/hooks/league/useLeagueSponsorshipsPageData.ts @@ -9,7 +9,7 @@ export function useLeagueSponsorshipsPageData(leagueId: string, currentDriverId: return usePageDataMultiple({ league: { queryKey: ['leagueDetail', leagueId, currentDriverId], - queryFn: () => leagueService.getLeagueDetail(leagueId), + queryFn: () => leagueService.getLeagueDetailData(leagueId), }, membership: { queryKey: ['leagueMembership', leagueId, currentDriverId], diff --git a/apps/website/hooks/league/useLeagueWalletPageData.ts b/apps/website/hooks/league/useLeagueWalletPageData.ts index 65db3e864..0381bcab2 100644 --- a/apps/website/hooks/league/useLeagueWalletPageData.ts +++ b/apps/website/hooks/league/useLeagueWalletPageData.ts @@ -17,25 +17,24 @@ export function useLeagueWalletPageData(leagueId: string) { // Transform DTO to ViewModel at client boundary const transactions = dto.transactions.map(t => new WalletTransactionViewModel({ id: t.id, - type: t.type, + type: t.type as any, description: t.description, amount: t.amount, - fee: t.fee, - netAmount: t.netAmount, - date: new globalThis.Date(t.date), + fee: 0, + netAmount: t.amount, + date: new globalThis.Date(t.createdAt), status: t.status, - reference: t.reference, })); return new LeagueWalletViewModel({ balance: dto.balance, currency: dto.currency, - totalRevenue: dto.totalRevenue, - totalFees: dto.totalFees, - totalWithdrawals: dto.totalWithdrawals, - pendingPayouts: dto.pendingPayouts, + totalRevenue: dto.balance, // Fallback + totalFees: 0, + totalWithdrawals: 0, + pendingPayouts: 0, transactions, - canWithdraw: dto.canWithdraw, - withdrawalBlockReason: dto.withdrawalBlockReason, + canWithdraw: true, + withdrawalBlockReason: undefined, }); }, enabled: !!leagueId, diff --git a/apps/website/lib/display-objects/MedalDisplay.ts b/apps/website/lib/display-objects/MedalDisplay.ts index ab3d102bd..f61afcf90 100644 --- a/apps/website/lib/display-objects/MedalDisplay.ts +++ b/apps/website/lib/display-objects/MedalDisplay.ts @@ -11,4 +11,22 @@ export class MedalDisplay { static getMedalIcon(position: number): string | null { return position <= 3 ? '🏆' : null; } + + static getBg(position: number): string { + switch (position) { + case 1: return 'bg-warning-amber'; + case 2: return 'bg-gray-300'; + case 3: return 'bg-orange-700'; + default: return 'bg-gray-800'; + } + } + + static getColor(position: number): string { + switch (position) { + case 1: return 'text-warning-amber'; + case 2: return 'text-gray-300'; + case 3: return 'text-orange-700'; + default: return 'text-gray-400'; + } + } } diff --git a/apps/website/lib/services/sponsors/SponsorService.ts b/apps/website/lib/services/sponsors/SponsorService.ts index 4416b03c6..e746ed6d8 100644 --- a/apps/website/lib/services/sponsors/SponsorService.ts +++ b/apps/website/lib/services/sponsors/SponsorService.ts @@ -52,4 +52,22 @@ export class SponsorService implements Service { async getSponsorshipPricing(): Promise { return this.apiClient.getPricing(); } + + async getAvailableLeagues(): Promise> { + try { + const data = await this.apiClient.getAvailableLeagues(); + return Result.ok(data); + } catch (error) { + return Result.err({ type: 'serverError', message: error instanceof Error ? error.message : 'Unknown error' }); + } + } + + async getLeagueDetail(leagueId: string): Promise> { + try { + const data = await this.apiClient.getLeagueDetail(leagueId); + return Result.ok(data); + } catch (error) { + return Result.err({ type: 'serverError', message: error instanceof Error ? error.message : 'Unknown error' }); + } + } } diff --git a/apps/website/lib/view-models/WalletTransactionViewModel.ts b/apps/website/lib/view-models/WalletTransactionViewModel.ts index c2cd4351f..6ec2b5e09 100644 --- a/apps/website/lib/view-models/WalletTransactionViewModel.ts +++ b/apps/website/lib/view-models/WalletTransactionViewModel.ts @@ -1,7 +1,7 @@ // Export the DTO type that WalletTransactionViewModel expects export type FullTransactionDto = { id: string; - type: 'sponsorship' | 'membership' | 'withdrawal' | 'prize'; + type: 'sponsorship' | 'membership' | 'withdrawal' | 'prize' | 'deposit'; description: string; amount: number; fee: number; @@ -13,7 +13,7 @@ export type FullTransactionDto = { export class WalletTransactionViewModel { id: string; - type: 'sponsorship' | 'membership' | 'withdrawal' | 'prize'; + type: 'sponsorship' | 'membership' | 'withdrawal' | 'prize' | 'deposit'; description: string; amount: number; fee: number; diff --git a/apps/website/next.config.mjs b/apps/website/next.config.mjs index 804d5a0dc..d2624e0f7 100644 --- a/apps/website/next.config.mjs +++ b/apps/website/next.config.mjs @@ -7,6 +7,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url)); const nextConfig = { reactStrictMode: true, + output: 'standalone', // Fix for monorepos: point tracing to repo root (portable across machines/containers) outputFileTracingRoot: path.join(__dirname, '../..'), images: { diff --git a/apps/website/templates/LeagueDetailTemplate.tsx b/apps/website/templates/LeagueDetailTemplate.tsx index 8bc2b0262..eaa90903d 100644 --- a/apps/website/templates/LeagueDetailTemplate.tsx +++ b/apps/website/templates/LeagueDetailTemplate.tsx @@ -8,12 +8,13 @@ import { SharedLink, SharedText, SharedStack, - SharedContainer + SharedContainer, + SharedIcon } from '@/components/shared/UIComponents'; import { ChevronRight } from 'lucide-react'; import { TemplateProps } from '@/lib/contracts/components/ComponentContracts'; -export function LeagueDetailTemplate({ viewData }: TemplateProps) { +export function LeagueDetailTemplate({ viewData, children, tabs }: TemplateProps & { children?: React.ReactNode, tabs?: any[] }) { return ( @@ -27,11 +28,10 @@ export function LeagueDetailTemplate({ viewData }: TemplateProps{viewData.name} + {children} {/* ... rest of the template ... */} ); } - -import { SharedIcon } from '@/components/shared/UIComponents'; diff --git a/apps/website/templates/TeamDetailTemplate.tsx b/apps/website/templates/TeamDetailTemplate.tsx index bd4c6e9d0..b4a10dd47 100644 --- a/apps/website/templates/TeamDetailTemplate.tsx +++ b/apps/website/templates/TeamDetailTemplate.tsx @@ -50,7 +50,7 @@ export function TeamDetailTemplate({ return ( - + Synchronizing Telemetry... diff --git a/apps/website/templates/TeamsTemplate.tsx b/apps/website/templates/TeamsTemplate.tsx index de5bdbde3..f3cd538d7 100644 --- a/apps/website/templates/TeamsTemplate.tsx +++ b/apps/website/templates/TeamsTemplate.tsx @@ -5,7 +5,7 @@ import { TeamGrid } from '@/components/teams/TeamGrid'; import { TeamLeaderboardPreview } from '@/components/teams/TeamLeaderboardPreviewWrapper'; import { TeamsDirectoryHeader } from '@/components/teams/TeamsDirectoryHeader'; import { TeamsDirectory, TeamsDirectorySection } from '@/components/teams/TeamsDirectory'; -import { SharedEmptyState } from '@/components/shared/SharedEmptyState'; +import { SharedEmptyState } from '@/components/shared/UIComponents'; import type { TeamsViewData } from '@/lib/view-data/TeamsViewData'; import { Users } from 'lucide-react'; import { TemplateProps } from '@/lib/contracts/components/ComponentContracts'; diff --git a/apps/website/templates/layout/GlobalSidebarTemplate.tsx b/apps/website/templates/layout/GlobalSidebarTemplate.tsx index 1dd073689..ee2cc53e5 100644 --- a/apps/website/templates/layout/GlobalSidebarTemplate.tsx +++ b/apps/website/templates/layout/GlobalSidebarTemplate.tsx @@ -4,7 +4,7 @@ import { AuthedNav } from '@/components/layout/AuthedNav'; import { PublicNav } from '@/components/layout/PublicNav'; import { useCurrentSession } from '@/hooks/auth/useCurrentSession'; import { Box } from '@/ui/Box'; -import { DashboardRail } from '@/ui/DashboardRail'; +import { DashboardRail } from '@/components/dashboard/DashboardRail'; import { Text } from '@/ui/Text'; import { usePathname } from 'next/navigation'; diff --git a/apps/website/templates/layout/HeaderContentTemplate.tsx b/apps/website/templates/layout/HeaderContentTemplate.tsx index 32dad1aa8..ff1111958 100644 --- a/apps/website/templates/layout/HeaderContentTemplate.tsx +++ b/apps/website/templates/layout/HeaderContentTemplate.tsx @@ -1,4 +1,4 @@ -import { BrandMark } from '@/components/layout/BrandMark'; +import { BrandMark } from '@/ui/BrandMark'; import { HeaderActions } from '@/components/layout/HeaderActions'; import { PublicNav } from '@/components/layout/PublicNav'; import { useCurrentSession } from '@/hooks/auth/useCurrentSession'; diff --git a/apps/website/tsconfig.json b/apps/website/tsconfig.json index 9a31a3eae..bf2b7db75 100644 --- a/apps/website/tsconfig.json +++ b/apps/website/tsconfig.json @@ -70,13 +70,17 @@ "include": [ "app/", "components/", + "ui/", + "templates/", + "hooks/", + "client-wrapper/", "lib/", "next-env.d.ts", "env.d.ts", "types/", "utilities/", ".next/types/**/*.ts" -, "hooks/sponsor/useSponsorMode.ts" ], + ], "exclude": [ "**/*.test.ts", "**/*.test.tsx", diff --git a/apps/website/ui/Accordion.tsx b/apps/website/ui/Accordion.tsx index 75142e122..4aee349fb 100644 --- a/apps/website/ui/Accordion.tsx +++ b/apps/website/ui/Accordion.tsx @@ -9,19 +9,35 @@ export interface AccordionProps { title: string; children: ReactNode; defaultOpen?: boolean; + isOpen?: boolean; + onToggle?: () => void; } export const Accordion = ({ title, children, - defaultOpen = false + defaultOpen = false, + isOpen: controlledIsOpen, + onToggle }: AccordionProps) => { - const [isOpen, setIsOpen] = useState(defaultOpen); + const [internalIsOpen, setInternalIsOpen] = useState(defaultOpen); + + const isControlled = controlledIsOpen !== undefined; + const isOpen = isControlled ? controlledIsOpen : internalIsOpen; + + const handleToggle = () => { + if (onToggle) { + onToggle(); + } + if (!isControlled) { + setInternalIsOpen(!internalIsOpen); + } + }; return ( + )} diff --git a/apps/website/ui/LeaderboardTableShell.tsx b/apps/website/ui/LeaderboardTableShell.tsx index cb21ed8ba..f7856a8e2 100644 --- a/apps/website/ui/LeaderboardTableShell.tsx +++ b/apps/website/ui/LeaderboardTableShell.tsx @@ -4,9 +4,10 @@ import { Surface } from './Surface'; export interface LeaderboardTableShellProps { children: ReactNode; + columns?: any[]; } -export const LeaderboardTableShell = ({ children }: LeaderboardTableShellProps) => { +export const LeaderboardTableShell = ({ children, columns }: LeaderboardTableShellProps) => { return ( diff --git a/apps/website/ui/LeagueCard.tsx b/apps/website/ui/LeagueCard.tsx index bf2707dda..071be6783 100644 --- a/apps/website/ui/LeagueCard.tsx +++ b/apps/website/ui/LeagueCard.tsx @@ -2,6 +2,7 @@ import { ChevronRight } from 'lucide-react'; import { ReactNode } from 'react'; import { Box } from './Box'; import { Card } from './Card'; +import { Icon } from './Icon'; import { Image } from './Image'; import { Text } from './Text'; diff --git a/apps/website/ui/Link.tsx b/apps/website/ui/Link.tsx index 0b06ed11d..0364ff92c 100644 --- a/apps/website/ui/Link.tsx +++ b/apps/website/ui/Link.tsx @@ -10,6 +10,20 @@ export interface LinkProps extends AnchorHTMLAttributes { block?: boolean; hoverColor?: string; transition?: boolean; + pb?: number; + truncate?: boolean; + hoverTextColor?: string; + display?: string; + alignItems?: string; + gap?: number; + rounded?: string; + bg?: string; + px?: number; + py?: number; + border?: boolean; + borderColor?: string; + shadow?: string; + hoverBorderColor?: string; } export const Link = forwardRef(({ @@ -22,6 +36,20 @@ export const Link = forwardRef(({ block = false, hoverColor, transition = true, + pb, + truncate, + hoverTextColor, + display, + alignItems, + gap, + rounded, + bg, + px, + py, + border, + borderColor, + shadow, + hoverBorderColor, ...props }, ref) => { const variantClasses = { @@ -43,12 +71,24 @@ export const Link = forwardRef(({ block ? 'block' : 'inline', variantClasses[variant], underlineClasses[underline], + truncate ? 'truncate' : '', ].join(' '); const style: React.CSSProperties = { ...(size ? { fontSize: size } : {}), ...(weight ? { fontWeight: weight } : {}), ...(letterSpacing ? { letterSpacing } : {}), + ...(pb ? { paddingBottom: `${pb * 0.25}rem` } : {}), + ...(display ? { display } : {}), + ...(alignItems ? { alignItems } : {}), + ...(gap ? { gap: `${gap * 0.25}rem` } : {}), + ...(rounded ? { borderRadius: rounded === 'full' ? '9999px' : `var(--ui-radius-${rounded})` } : {}), + ...(bg ? { backgroundColor: bg.startsWith('bg-') ? undefined : bg } : {}), + ...(px ? { paddingLeft: `${px * 0.25}rem`, paddingRight: `${px * 0.25}rem` } : {}), + ...(py ? { paddingTop: `${py * 0.25}rem`, paddingBottom: `${py * 0.25}rem` } : {}), + ...(border ? { border: '1px solid var(--ui-color-border-default)' } : {}), + ...(borderColor ? { borderColor: borderColor.startsWith('border-') ? undefined : borderColor } : {}), + ...(shadow ? { boxShadow: shadow.startsWith('shadow-') ? undefined : shadow } : {}), }; return ( diff --git a/apps/website/ui/MiniStat.tsx b/apps/website/ui/MiniStat.tsx index 5caba32b2..bf927415c 100644 --- a/apps/website/ui/MiniStat.tsx +++ b/apps/website/ui/MiniStat.tsx @@ -5,16 +5,18 @@ export interface MiniStatProps { label: string; value: string | number; intent?: 'primary' | 'success' | 'warning' | 'critical' | 'high' | 'med' | 'low'; + color?: string; } export const MiniStat = ({ label, value, - intent = 'high' + intent = 'high', + color }: MiniStatProps) => { return ( - {value} + {value} {label} ); diff --git a/apps/website/ui/NotificationContent.tsx b/apps/website/ui/NotificationContent.tsx index d9c11b933..e72f5d7ce 100644 --- a/apps/website/ui/NotificationContent.tsx +++ b/apps/website/ui/NotificationContent.tsx @@ -1,3 +1,4 @@ +import { LucideIcon } from 'lucide-react'; import { Box } from './Box'; import { Icon } from './Icon'; import { Text } from './Text'; diff --git a/apps/website/ui/PageHero.tsx b/apps/website/ui/PageHero.tsx index aa09df1b5..9821890f1 100644 --- a/apps/website/ui/PageHero.tsx +++ b/apps/website/ui/PageHero.tsx @@ -1,6 +1,9 @@ +import { LucideIcon } from 'lucide-react'; import { ReactNode } from 'react'; import { Box } from './Box'; +import { Button } from './Button'; import { Heading } from './Heading'; +import { Icon } from './Icon'; import { Surface } from './Surface'; import { Text } from './Text'; @@ -9,13 +12,22 @@ export interface PageHeroProps { description?: string; children?: ReactNode; image?: ReactNode; + icon?: LucideIcon; + actions?: Array<{ + label: string; + onClick: () => void; + variant?: 'primary' | 'secondary'; + icon?: LucideIcon; + }>; } export const PageHero = ({ title, description, children, - image + image, + icon, + actions }: PageHeroProps) => { return ( - {title} + + {icon && ( + + + + )} + {title} + {description && ( {description} )} + {actions && ( + + {actions.map((action) => ( + + ))} + + )} {children} {image && ( diff --git a/apps/website/ui/Panel.tsx b/apps/website/ui/Panel.tsx index d7568a386..70a31726c 100644 --- a/apps/website/ui/Panel.tsx +++ b/apps/website/ui/Panel.tsx @@ -12,6 +12,11 @@ export interface PanelProps { variant?: 'default' | 'dark' | 'muted'; padding?: Spacing; actions?: ReactNode; + className?: string; + border?: boolean; + rounded?: string; + borderColor?: string; + bg?: string; } export const Panel = ({ @@ -21,10 +26,23 @@ export const Panel = ({ footer, variant = 'default', padding = 6, - actions + actions, + className, + border, + rounded, + borderColor, + bg }: PanelProps) => { return ( - + {(title || description || actions) && ( diff --git a/apps/website/ui/PasswordField.tsx b/apps/website/ui/PasswordField.tsx index b770d2ae9..41dc79929 100644 --- a/apps/website/ui/PasswordField.tsx +++ b/apps/website/ui/PasswordField.tsx @@ -4,10 +4,29 @@ import { Box } from './Box'; import { IconButton } from './IconButton'; import { Input, InputProps } from './Input'; -export interface PasswordFieldProps extends InputProps {} +export interface PasswordFieldProps extends InputProps { + showPassword?: boolean; + onTogglePassword?: () => void; +} -export const PasswordField = (props: PasswordFieldProps) => { - const [showPassword, setShowPassword] = useState(false); +export const PasswordField = ({ + showPassword: controlledShowPassword, + onTogglePassword, + ...props +}: PasswordFieldProps) => { + const [internalShowPassword, setInternalShowPassword] = useState(false); + + const isControlled = controlledShowPassword !== undefined; + const showPassword = isControlled ? controlledShowPassword : internalShowPassword; + + const handleToggle = () => { + if (onTogglePassword) { + onTogglePassword(); + } + if (!isControlled) { + setInternalShowPassword(!internalShowPassword); + } + }; return ( @@ -24,7 +43,7 @@ export const PasswordField = (props: PasswordFieldProps) => { > setShowPassword(!showPassword)} + onClick={handleToggle} variant="ghost" size="sm" title={showPassword ? 'Hide password' : 'Show password'} diff --git a/apps/website/ui/QuickActionLink.tsx b/apps/website/ui/QuickActionLink.tsx index 620c7acc4..7416f67db 100644 --- a/apps/website/ui/QuickActionLink.tsx +++ b/apps/website/ui/QuickActionLink.tsx @@ -5,15 +5,19 @@ import { Link } from './Link'; import { Text } from './Text'; export interface QuickActionLinkProps { - label: string; - icon: LucideIcon; + label?: string; + icon?: LucideIcon; href: string; + children?: React.ReactNode; + variant?: string; } export const QuickActionLink = ({ label, icon, - href + href, + children, + variant }: QuickActionLinkProps) => { return ( @@ -24,11 +28,15 @@ export const QuickActionLink = ({ paddingY={2} className="group" > - - - {label} - - + {children ? children : ( + <> + {icon && } + + {label} + + + + )} ); diff --git a/apps/website/ui/Section.tsx b/apps/website/ui/Section.tsx index 20c0fd765..66b2dddf1 100644 --- a/apps/website/ui/Section.tsx +++ b/apps/website/ui/Section.tsx @@ -6,13 +6,19 @@ export interface SectionProps { variant?: 'default' | 'dark' | 'muted'; padding?: 'none' | 'sm' | 'md' | 'lg'; id?: string; + minHeight?: string; + py?: number; + className?: string; } export const Section = ({ children, variant = 'default', padding = 'md', - id + id, + minHeight, + py, + className }: SectionProps) => { const variantClasses = { default: 'bg-[var(--ui-color-bg-base)]', @@ -29,11 +35,15 @@ export const Section = ({ const classes = [ variantClasses[variant], - paddingClasses[padding], + py !== undefined ? '' : paddingClasses[padding], + className, ].join(' '); return ( -
+
{children} diff --git a/apps/website/ui/SegmentedControl.tsx b/apps/website/ui/SegmentedControl.tsx index 2386c93d5..8d3a4308c 100644 --- a/apps/website/ui/SegmentedControl.tsx +++ b/apps/website/ui/SegmentedControl.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { Surface } from './Surface'; export interface SegmentedControlOption { id: string; diff --git a/apps/website/ui/Select.tsx b/apps/website/ui/Select.tsx index 9a80b46c0..b89c37d01 100644 --- a/apps/website/ui/Select.tsx +++ b/apps/website/ui/Select.tsx @@ -14,6 +14,7 @@ export interface SelectProps extends Omit(({ @@ -23,6 +24,7 @@ export const Select = forwardRef(({ hint, fullWidth = false, size = 'md', + pl, ...props }, ref) => { const sizeClasses = { @@ -42,6 +44,10 @@ export const Select = forwardRef(({ widthClasses, ].filter(Boolean).join(' '); + const style: React.CSSProperties = { + ...(pl !== undefined ? { paddingLeft: `${pl * 0.25}rem` } : {}), + }; + return ( {label && ( @@ -55,6 +61,7 @@ export const Select = forwardRef(({