diff --git a/src/app.config.ts b/src/app.config.ts index d477846..45393cd 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -1,10 +1,12 @@ type AppConfig = { name: string; + description: string; navigation: Array; }; export const appConfig: AppConfig = { name: "Game Master", + description: "Challenge your friends in the ultimate quiz battle!", navigation: [ { label: "Home", @@ -16,7 +18,7 @@ export const appConfig: AppConfig = { }, { label: "Games", - path: "/#games", + path: "/game", }, ], }; diff --git a/src/app/(routes)/game/page.tsx b/src/app/(routes)/game/page.tsx new file mode 100644 index 0000000..ec0dd3f --- /dev/null +++ b/src/app/(routes)/game/page.tsx @@ -0,0 +1,7 @@ +import React from "react"; + +function GamesPage() { + return
GamesPage
; +} + +export default GamesPage; diff --git a/src/app/(routes)/layout.tsx b/src/app/(routes)/layout.tsx index 79ebb03..10f6487 100644 --- a/src/app/(routes)/layout.tsx +++ b/src/app/(routes)/layout.tsx @@ -1,11 +1,15 @@ import React from "react"; import Navbar from "@/components/navbar"; +import Footer from "@/components/footer"; function Layout({ children }: { children: React.ReactNode }) { return ( -
+
- {children} +
+ {children} +
+
); } diff --git a/src/app/(routes)/lobby/page.tsx b/src/app/(routes)/lobby/page.tsx index fbfe118..eddb693 100644 --- a/src/app/(routes)/lobby/page.tsx +++ b/src/app/(routes)/lobby/page.tsx @@ -1,20 +1,35 @@ import React from "react"; import { api } from "@/trpc/server"; -import Link from "next/link"; +import CreateLobbyDialog from "@/app/_components/create-lobby-dialog"; +import LobbyCard from "@/app/_components/lobby-card"; +import { Button } from "@/components/ui/button"; async function Page() { const lobbies = await api.lobby.getAll(); return ( -
- - {lobbies.map((lobby) => ( -
  • - {lobby.name} -
  • - ))} -
    -
    + <> +
    +
    + + +
    + + + {lobbies.map((lobby) => ( +
  • + +
  • + ))} +
    +
    + ); } diff --git a/src/app/(routes)/page.tsx b/src/app/(routes)/page.tsx index 7db5ccf..cc4c133 100644 --- a/src/app/(routes)/page.tsx +++ b/src/app/(routes)/page.tsx @@ -4,40 +4,29 @@ import { Sparkles, Users, Plus } from "lucide-react"; import CreateLobbyDialog from "../_components/create-lobby-dialog"; import { auth } from "@/server/auth"; import { Icons } from "@/components/icons"; +import { appConfig } from "@/app.config"; export default async function QuizGameStartPage() { const session = await auth(); return ( -
    -
    -
    - -
    -

    - QUIZ MASTER + <> +
    + + +

    + {appConfig.name}

    - Challenge your friends in the ultimate quiz battle! + {appConfig.description}

    {/* Game card */} -
    +
    {/* Create Lobby Button */}
    {session ? ( - - - + ) : ( @@ -61,19 +54,6 @@ export default async function QuizGameStartPage() {
    - - {/* Footer */} -
    - - How to Play - - - Leaderboard - - - Settings - -
    -

    + ); } diff --git a/src/app/(routes)/profile/[id]/page.tsx b/src/app/(routes)/profile/[id]/page.tsx index 5df847d..9c56b81 100644 --- a/src/app/(routes)/profile/[id]/page.tsx +++ b/src/app/(routes)/profile/[id]/page.tsx @@ -15,16 +15,18 @@ import { Sparkles, } from "lucide-react"; import { auth } from "@/server/auth"; +import Avatar from "@/components/avatar"; +import { formatDate } from "@/lib/utils"; export default async function ProfilePage() { const session = await auth(); + // Mock user data - in a real app this would come from a database const user = { - username: "QuizMaster42", + ...session?.user, level: 24, xp: 7840, xpToNextLevel: 10000, - joinDate: "March 2023", gamesPlayed: 187, gamesWon: 112, winRate: 59.9, @@ -63,280 +65,240 @@ export default async function ProfilePage() { const xpProgress = (user.xp / user.xpToNextLevel) * 100; return ( -
    - {/* Main content */} -
    -
    - {/* Profile Card */} -
    -
    -
    -
    -
    - Profile +
    +
    + {/* Profile Card */} +
    +
    +
    +
    +
    + +
    +
    +
    + {user.level} +
    +
    + +

    + {user.name} +

    + +

    + Member since{" "} + {new Date(user?.joinedAt!)?.toLocaleDateString("en-EN", { + year: "numeric", + month: "short", + })} +

    + + {/* XP Progress */} +
    + {/*
    + XP: {user.xp.toLocaleString()} + {user.xpToNextLevel.toLocaleString()} +
    */} +
    +
    +
    +
    + {Math.floor(user.xpToNextLevel - user.xp).toLocaleString()} XP + to level {user.level + 1} +
    +
    + + {/* Stats */} +
    +
    +

    + {user.gamesPlayed} +

    +

    Games

    +
    +
    +

    {user.gamesWon}

    +

    Wins

    +
    +
    +

    {user.winRate}%

    +

    Win Rate

    +
    +
    + {session ? ( + + ) : null} +
    +
    + + {/* Achievements and Recent Games */} +
    + {/* Achievements */} +
    +
    + +

    Achievements

    +
    + +
    + {user.badges.map((badge, index) => ( +
    +
    + +
    +
    +

    {badge.name}

    -
    - {user.level} + ))} + +
    +
    + +
    +
    +

    + Locked Achievement +

    - -

    - {user.username} -

    -

    - Member since {user.joinDate} -

    - - {/* XP Progress */} -
    -
    - XP: {user.xp.toLocaleString()} - {user.xpToNextLevel.toLocaleString()} -
    -
    -
    -
    -
    - {Math.floor(user.xpToNextLevel - user.xp).toLocaleString()} XP - to level {user.level + 1} -
    -
    - - {/* Stats */} -
    -
    -

    - {user.gamesPlayed} -

    -

    Games

    -
    -
    -

    - {user.gamesWon} -

    -

    Wins

    -
    -
    -

    - {user.winRate}% -

    -

    Win Rate

    -
    -
    - {session ? ( - - - - ) : ( -
    - )}
    - {/* Achievements and Recent Games */} -
    - {/* Achievements */} -
    -
    - -

    Achievements

    -
    + {/* Stats Overview */} +
    +
    + +

    Stats Overview

    +
    -
    - {user.badges.map((badge, index) => ( -
    -
    - +
    +
    +

    Top Categories

    +
    +
    +
    + Science + 92%
    -
    -

    {badge.name}

    +
    +
    - ))} - -
    -
    - -
    -

    - Locked Achievement +

    + History + 78% +
    +
    +
    +
    +
    +
    +
    + Movies + 65% +
    +
    +
    +
    +
    +
    +
    + +
    +

    Response Time

    +
    +
    +

    3.2s

    +

    + Average response time +

    +

    + Faster than 75% of players

    +
    - {/* Stats Overview */} -
    -
    - -

    Stats Overview

    -
    - -
    -
    -

    - Top Categories -

    -
    -
    -
    - Science - 92% -
    -
    -
    -
    -
    -
    -
    - History - 78% -
    -
    -
    -
    -
    -
    -
    - Movies - 65% -
    -
    -
    -
    -
    -
    -
    - -
    -

    - Response Time -

    -
    -
    -

    3.2s

    -

    - Average response time -

    -

    - Faster than 75% of players -

    -
    -
    -
    -
    + {/* Recent Games */} +
    +
    + +

    Recent Games

    - {/* Recent Games */} -
    -
    - -

    Recent Games

    -
    - -
    - {user.recentGames.map((game) => ( -
    -
    - {game.position === "1st" ? ( - - ) : game.position === "2nd" ? ( - - ) : ( - - )} -
    -
    -
    -

    {game.topic}

    - - {game.date} - -
    -
    - - Position:{" "} - {game.position} - - - Score:{" "} - {game.score} - -
    -
    -
    - ))} - - -
    +
    + {game.position === "1st" ? ( + + ) : game.position === "2nd" ? ( + + ) : ( + + )} +
    +
    +
    +

    {game.topic}

    + + {game.date} + +
    +
    + + Position:{" "} + {game.position} + + + Score: {game.score} + +
    +
    +
    + ))} + +
    -
    - - {/* Footer */} -
    -
    -
    - - QUIZ MASTER -
    -
    - - Help - - - Privacy - - - Terms - -
    -
    -
    -
    +
    +
    ); } diff --git a/src/app/_components/create-lobby-dialog.tsx b/src/app/_components/create-lobby-dialog.tsx index 4cd017f..8b0095c 100644 --- a/src/app/_components/create-lobby-dialog.tsx +++ b/src/app/_components/create-lobby-dialog.tsx @@ -8,7 +8,6 @@ import { Button } from "@/components/ui/button"; import { Form, FormControl, - FormDescription, FormField, FormItem, FormLabel, @@ -27,8 +26,10 @@ import { import { toast } from "sonner"; import { api } from "@/trpc/react"; import { useRouter } from "next/navigation"; +import { Icons } from "@/components/icons"; +import { cn } from "@/lib/utils"; -function CreateLobbyDialog({ children }: { children: React.ReactNode }) { +function CreateLobbyDialog({ className }: { className?: string }) { const [open, setOpen] = React.useState(false); const [loading, setLoading] = React.useState(false); const { mutateAsync } = api.lobby.create.useMutation(); @@ -51,9 +52,23 @@ function CreateLobbyDialog({ children }: { children: React.ReactNode }) { return ( - {children} + + + - + Create a Lobby @@ -67,6 +82,7 @@ function CreateLobbyDialog({ children }: { children: React.ReactNode }) { Name diff --git a/src/app/_components/lobby-card.tsx b/src/app/_components/lobby-card.tsx new file mode 100644 index 0000000..82410be --- /dev/null +++ b/src/app/_components/lobby-card.tsx @@ -0,0 +1,35 @@ +import React from "react"; +import type { Lobby } from "@/server/db/schema"; +import { + Card, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import Link from "next/link"; +import { formatDate } from "@/lib/utils"; +import { ArrowRight, ChevronRight } from "lucide-react"; + +function LobbyCard({ lobby }: { lobby: Lobby }) { + return ( + + +
    + +
    + {lobby.name} + + {formatDate(lobby.createdAt)} + +
    +
    + +
    +
    +
    +
    + + ); +} + +export default LobbyCard; diff --git a/src/app/_components/lobby-page.tsx b/src/app/_components/lobby-page.tsx index 591a69c..0b9d068 100644 --- a/src/app/_components/lobby-page.tsx +++ b/src/app/_components/lobby-page.tsx @@ -8,7 +8,11 @@ import DeleteLobbyDialog from "@/app/_components/delete-lobby-dialog"; import LobbyMembershipDialog from "@/app/_components/lobby-membership-dialog"; import { type Session } from "next-auth"; import { getSocket } from "@/lib/hooks/use-socket"; -import { LOBBY_USER_PRESENCE_EVENT } from "@/server/socket/event-const"; +import { + LOBBY_USER_PRESENCE_EVENT, + LOBBY_USER_PRESENCE_UPDATE_EVENT, +} from "@/server/socket/event-const"; +import { Badge } from "@/components/ui/badge"; function LobbyPage({ session, @@ -20,6 +24,7 @@ function LobbyPage({ initialMembers: Array<{ leader: boolean } & PublicUser>; }) { const [members, setMembers] = React.useState(initialMembers); + const [memberPresence, setMemberPresence] = React.useState>(); const isJoined = members.find((member) => member.id === session?.user.id); const isOwner = lobby.createdById === session?.user.id; const socket = session ? getSocket() : undefined; @@ -27,6 +32,13 @@ function LobbyPage({ React.useEffect(() => { if (!session || !isJoined || !socket) return; socket.emit(LOBBY_USER_PRESENCE_EVENT, lobby.id, session.user.id, true); + console.log("user joined"); + + // socket.on(LOBBY_USER_PRESENCE_UPDATE_EVENT, (users) => { + // console.log("presence updated", users); + + // setMemberPresence(users); + // }); return () => { if (!session || !isJoined || !socket) return; @@ -36,17 +48,26 @@ function LobbyPage({ }, [socket]); return ( -
    +

    {lobby.name}

    -
      +
        {members?.map((member, idx) => (
      • - - {member?.leader && } + + {member?.leader && ( + + Leader + + )}
      • ))}
      + {JSON.stringify(memberPresence)} {isOwner && } {!isOwner && ( diff --git a/src/app/_components/user-popover.tsx b/src/app/_components/user-popover.tsx index 63b9fab..60a873c 100644 --- a/src/app/_components/user-popover.tsx +++ b/src/app/_components/user-popover.tsx @@ -1,9 +1,3 @@ -import { Button, buttonVariants } from "@/components/ui/button"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; import Link from "next/link"; import React from "react"; import { @@ -14,31 +8,52 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; -import { cn } from "@/lib/utils"; import type { User } from "next-auth"; +import Avatar from "@/components/avatar"; +import { Edit, Eye, LogOut } from "lucide-react"; +import { cn } from "@/lib/utils"; -function UserPopover({ - children, - user, -}: { - children: React.ReactNode; - user: User; -}) { +function UserPopover({ user }: { user: User }) { + const dropdownItemClassName = + "focus:bg-border/30 focus:text-white flex items-center gap-1 "; return ( - {children} - - {user.name} + + + + + + {user.name} + - - View Profile + + + + View Profile + - - Edit Profile + + + + Edit Profile + - - Log out + + + + Log out + diff --git a/src/components/footer.tsx b/src/components/footer.tsx new file mode 100644 index 0000000..3a996e1 --- /dev/null +++ b/src/components/footer.tsx @@ -0,0 +1,39 @@ +import Link from "next/link"; +import React from "react"; +import { Icons } from "./icons"; +import { appConfig } from "@/app.config"; + +function Footer() { + return ( +
      +
      +
      + + {appConfig.name} +
      +
      + + Help + + + Privacy + + + Terms + +
      +
      +
      + ); +} + +export default Footer; diff --git a/src/components/icons.tsx b/src/components/icons.tsx index 7f97410..f4258cd 100644 --- a/src/components/icons.tsx +++ b/src/components/icons.tsx @@ -13,14 +13,14 @@ export const Icons = { {...props} > diff --git a/src/components/nav-link.tsx b/src/components/nav-link.tsx index b55f1d7..a898ec3 100644 --- a/src/components/nav-link.tsx +++ b/src/components/nav-link.tsx @@ -5,13 +5,31 @@ import { Button } from "./ui/button"; import Link from "next/link"; import { Home } from "lucide-react"; import { usePathname } from "next/navigation"; +import { cn } from "@/lib/utils"; -function NavLink({ label, path }: NavLink) { +function NavLink({ + label, + path, + className, + isLast, +}: NavLink & { className?: string; isLast?: boolean }) { const active = usePathname() === path; return ( - ); diff --git a/src/components/navbar.tsx b/src/components/navbar.tsx index 5b43e32..edd95a6 100644 --- a/src/components/navbar.tsx +++ b/src/components/navbar.tsx @@ -1,9 +1,7 @@ import React from "react"; import { auth } from "@/server/auth"; -import Avatar from "./avatar"; import { Button } from "./ui/button"; import Link from "next/link"; -import { Home } from "lucide-react"; import UserPopover from "@/app/_components/user-popover"; import { appConfig } from "@/app.config"; import NavLink from "./nav-link"; @@ -12,24 +10,21 @@ async function Navbar() { const session = await auth(); return ( -