From 9fb200bcfe2661e48da0d736acee4a7b383d2e10 Mon Sep 17 00:00:00 2001 From: Keeton Martin Date: Thu, 28 Aug 2025 23:03:10 -0400 Subject: [PATCH] feat: redesign dashboard with dark mode --- index.html | 2 +- src/App.css | 59 ---------------- src/App.tsx | 98 ++++++++++++++------------- src/components/ui/app-header.tsx | 32 +++++++++ src/components/ui/button.tsx | 1 + src/components/ui/jsonCard.tsx | 2 +- src/components/ui/navigation-menu.tsx | 1 + src/components/ui/theme-toggle.tsx | 34 ++++++++++ src/components/ui/toggle.tsx | 1 + src/contexts/PlayersContext.tsx | 11 ++- src/contexts/ScheduleContext.tsx | 1 + 11 files changed, 130 insertions(+), 112 deletions(-) delete mode 100644 src/App.css create mode 100644 src/components/ui/app-header.tsx create mode 100644 src/components/ui/theme-toggle.tsx diff --git a/index.html b/index.html index 8fc0725..48406e3 100644 --- a/index.html +++ b/index.html @@ -1,5 +1,5 @@ - + diff --git a/src/App.css b/src/App.css deleted file mode 100644 index 766e89d..0000000 --- a/src/App.css +++ /dev/null @@ -1,59 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} - -.card-container { - display: flex; - justify-content: space-around; /* Adjust as needed */ - align-items: center; /* Adjust if needed */ -} - -.input-center { - display: flex; - justify-content: center; - align-items: center; -} - -.headerContainer { - display: flex; - justify-content: flex-end; /* Aligns child elements to the right */ -} diff --git a/src/App.tsx b/src/App.tsx index 3ce2717..de25b36 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,4 @@ import { useContext, useEffect, useState } from "react"; -import "./App.css"; -import { Title } from "./components/ui/title"; import { InputWithButton } from "./components/ui/inputWithButton"; import UserCard from "./components/ui/userCard"; import LeagueCard from "./components/ui/leagueCard"; @@ -9,18 +7,23 @@ import PlayersCard from "./components/ui/playersCard"; import { ScheduleContext, ScheduleProvider } from "./contexts/ScheduleContext"; // Import the ScheduleContext and Provider import WeekCard from "./components/ui/weekCard"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import Heading from "./components/ui/heading"; +import { AppHeader } from "./components/ui/app-header"; import { getCurrentNFLSeason } from "./lib/seasonUtils"; -// Define a type for your API response -// Replace 'any' with a more specific type if you know the structure of your API response -type ApiResponse = any | null; +// Define a type for the Sleeper league API response +type SleeperLeague = { + total_rosters: number; + name: string; + league_id: string; + avatar: string; +}; +type ApiResponse = unknown | null; export type FantasyFootballLeague = { numberOfTeams: number; leagueName: string; leagueId: string; - rosters: any; + rosters: unknown; avatar: string; }; @@ -34,7 +37,7 @@ function App() { const [userId, setUserId] = useState(null); const [displayName, setDisplayName] = useState(null); const [avatar, setAvatar] = useState(null); - const [leagueData, setLeagueData] = useState(null); // State to store the league data + const [leagueData, setLeagueData] = useState(null); // State to store the league data const [leagues, setLeagues] = useState([]); // State to store league objects const { setPlayers } = useContext(PlayersContext); // Use the setPlayers function from context @@ -85,7 +88,7 @@ function App() { fetchScheduleData(); if (leagueData) { - const mappedLeagues = leagueData.map((league: any) => ({ + const mappedLeagues = leagueData.map((league) => ({ numberOfTeams: league.total_rosters, leagueName: league.name, leagueId: league.league_id, @@ -159,51 +162,50 @@ function App() { return ( <> -
- -
- - <div className="input-center"> - <InputWithButton - username={username} - setUsername={setUsername} - fetchUserData={fetchUserData} - /> - </div> - <div className="card-container"> - <UserCard - username={username} - userId={userId} - displayName={displayName} - avatar={avatar} - /> - <WeekCard /> - </div> - - {/* Tabs Component */} - <Tabs defaultValue="players" className="w-full"> - <TabsList className="grid w-full grid-cols-2"> - <TabsTrigger value="players">All Players</TabsTrigger> - <TabsTrigger value="leagues">Leagues</TabsTrigger> - </TabsList> - <TabsContent value="players"> - <PlayersCard - leagues={leagues} + <AppHeader /> + <main className="container mx-auto space-y-6 p-4"> + <div className="flex justify-center"> + <InputWithButton + username={username} + setUsername={setUsername} + fetchUserData={fetchUserData} + /> + </div> + <div className="grid gap-4 md:grid-cols-2"> + <UserCard + username={username} userId={userId} displayName={displayName} + avatar={avatar} /> - </TabsContent> - <TabsContent value="leagues"> - {leagues.map((league, index) => ( - <LeagueCard - key={index} - league={league} + <WeekCard /> + </div> + + {/* Tabs Component */} + <Tabs defaultValue="players" className="w-full"> + <TabsList className="grid w-full grid-cols-2"> + <TabsTrigger value="players">All Players</TabsTrigger> + <TabsTrigger value="leagues">Leagues</TabsTrigger> + </TabsList> + <TabsContent value="players"> + <PlayersCard + leagues={leagues} userId={userId} displayName={displayName} /> - ))} - </TabsContent> - </Tabs> + </TabsContent> + <TabsContent value="leagues"> + {leagues.map((league, index) => ( + <LeagueCard + key={index} + league={league} + userId={userId} + displayName={displayName} + /> + ))} + </TabsContent> + </Tabs> + </main> </> ); } diff --git a/src/components/ui/app-header.tsx b/src/components/ui/app-header.tsx new file mode 100644 index 0000000..cfed260 --- /dev/null +++ b/src/components/ui/app-header.tsx @@ -0,0 +1,32 @@ +import { + NavigationMenu, + NavigationMenuItem, + NavigationMenuList, + navigationMenuTriggerStyle, +} from "@/components/ui/navigation-menu"; +import { ThemeToggle } from "@/components/ui/theme-toggle"; + +export function AppHeader() { + return ( + <header className="border-b"> + <div className="container mx-auto flex h-16 items-center justify-between px-4"> + <h1 className="text-xl font-bold">Gametime</h1> + <nav className="flex items-center gap-4"> + <NavigationMenu> + <NavigationMenuList> + <NavigationMenuItem> + <a + href="https://github.com/keetonmartin/gametime" + className={navigationMenuTriggerStyle()} + > + GitHub + </a> + </NavigationMenuItem> + </NavigationMenuList> + </NavigationMenu> + <ThemeToggle /> + </nav> + </div> + </header> + ); +} diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index 0270f64..d4a0d8a 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react-refresh/only-export-components */ import * as React from "react" import { Slot } from "@radix-ui/react-slot" import { cva, type VariantProps } from "class-variance-authority" diff --git a/src/components/ui/jsonCard.tsx b/src/components/ui/jsonCard.tsx index e32b7ee..729023c 100644 --- a/src/components/ui/jsonCard.tsx +++ b/src/components/ui/jsonCard.tsx @@ -9,7 +9,7 @@ import { // Define the types for the props interface JsonCardProps { username: string; - data: null | any; // Replace 'any' with a more specific type if you know the structure of 'data' + data: Record<string, unknown> | null; userId: string | null; } diff --git a/src/components/ui/navigation-menu.tsx b/src/components/ui/navigation-menu.tsx index 5841fb3..6413c7b 100644 --- a/src/components/ui/navigation-menu.tsx +++ b/src/components/ui/navigation-menu.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react-refresh/only-export-components */ import * as React from "react" import { ChevronDownIcon } from "@radix-ui/react-icons" import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu" diff --git a/src/components/ui/theme-toggle.tsx b/src/components/ui/theme-toggle.tsx new file mode 100644 index 0000000..23aac6e --- /dev/null +++ b/src/components/ui/theme-toggle.tsx @@ -0,0 +1,34 @@ +import { useEffect, useState } from "react"; +import { MoonIcon, SunIcon } from "@radix-ui/react-icons"; +import { Button } from "@/components/ui/button"; + +export function ThemeToggle() { + const [theme, setTheme] = useState<"light" | "dark">( + (localStorage.getItem("theme") as "light" | "dark") || "dark" + ); + + useEffect(() => { + const root = window.document.documentElement; + if (theme === "dark") { + root.classList.add("dark"); + } else { + root.classList.remove("dark"); + } + localStorage.setItem("theme", theme); + }, [theme]); + + return ( + <Button + variant="ghost" + size="icon" + onClick={() => setTheme(theme === "dark" ? "light" : "dark")} + aria-label="Toggle theme" + > + {theme === "dark" ? ( + <SunIcon className="h-4 w-4" /> + ) : ( + <MoonIcon className="h-4 w-4" /> + )} + </Button> + ); +} diff --git a/src/components/ui/toggle.tsx b/src/components/ui/toggle.tsx index 9923548..cd8fc00 100644 --- a/src/components/ui/toggle.tsx +++ b/src/components/ui/toggle.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react-refresh/only-export-components */ import * as React from "react" import * as TogglePrimitive from "@radix-ui/react-toggle" import { cva, type VariantProps } from "class-variance-authority" diff --git a/src/contexts/PlayersContext.tsx b/src/contexts/PlayersContext.tsx index 78aca9a..dd243a2 100644 --- a/src/contexts/PlayersContext.tsx +++ b/src/contexts/PlayersContext.tsx @@ -1,8 +1,13 @@ +/* eslint-disable react-refresh/only-export-components */ import React, { createContext, useState, ReactNode, useContext } from 'react'; +interface PlayersMap { + [key: string]: unknown; +} + interface PlayersContextType { - players: { [key: string]: any }; // Replace 'any' with a more specific type if possible - setPlayers: React.Dispatch<React.SetStateAction<{ [key: string]: any }>>; // Same as above + players: PlayersMap; + setPlayers: React.Dispatch<React.SetStateAction<PlayersMap>>; } export const PlayersContext = createContext<PlayersContextType>({ @@ -15,7 +20,7 @@ interface PlayersProviderProps { } export const PlayersProvider: React.FC<PlayersProviderProps> = ({ children }) => { - const [players, setPlayers] = useState<{ [key: string]: any }>({}); // Replace 'any' with a more specific type if possible + const [players, setPlayers] = useState<PlayersMap>({}); return ( <PlayersContext.Provider value={{ players, setPlayers }}> diff --git a/src/contexts/ScheduleContext.tsx b/src/contexts/ScheduleContext.tsx index f6518a6..ebbab09 100644 --- a/src/contexts/ScheduleContext.tsx +++ b/src/contexts/ScheduleContext.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react-refresh/only-export-components */ import React, { createContext, useState, ReactNode, useContext } from 'react'; interface Team {