diff --git a/app/(content)/(gaming)/games/page.tsx b/app/(content)/(gaming)/games/page.tsx index 4ff601ba9e793611a00380291f9f29d1510f6ae6..81b91728af8c7021d3f6b122f32c00ba5d3b0d9f 100644 --- a/app/(content)/(gaming)/games/page.tsx +++ b/app/(content)/(gaming)/games/page.tsx @@ -1,7 +1,6 @@ import Sort from "@/components/filter-sort-games"; import { InfiniteScrollGames } from "@/components/infinity-scroll"; import ScrollToTop from "@/components/scroll-to-top"; -import SearchInput from "@/components/search-input"; // renders a list of games infinitely export default async function GamesPage() { @@ -9,9 +8,6 @@ export default async function GamesPage() { <> <main className="relative lg:gap-10 xl:grid xl:grid-cols-[1fr_240px]"> <div className="grid"> - <div className="flex flex-col gap-10 items-center w-full"> - <SearchInput className="p-3 lg:w-2/3 2xl:w-1/3" /> - </div> <InfiniteScrollGames /> </div> <div className="hidden xl:block flex-col md:flex"> diff --git a/app/(content)/followers/page.tsx b/app/(content)/(user)/followers/page.tsx similarity index 100% rename from app/(content)/followers/page.tsx rename to app/(content)/(user)/followers/page.tsx diff --git a/app/(content)/notifications/page.tsx b/app/(content)/(user)/notifications/page.tsx similarity index 100% rename from app/(content)/notifications/page.tsx rename to app/(content)/(user)/notifications/page.tsx diff --git a/app/(content)/layout.tsx b/app/(content)/layout.tsx index 8a4498245c07dd8339d429a373d433c49cd67938..3c53c0a1715f24a9235da32dc946be7385c556a8 100644 --- a/app/(content)/layout.tsx +++ b/app/(content)/layout.tsx @@ -1,5 +1,8 @@ +import { MainNav } from "@/components/header" import DashboardNav from "@/components/nav" +import SearchInput from "@/components/search-input" import { SiteFooter } from "@/components/site-footer" +import { UserAccountNav } from "@/components/user-nav" import { dashboardConfig } from "@/lib/config/dashboard" import { getCurrentUser } from "@/lib/session" @@ -12,12 +15,24 @@ export default async function ContentLayout({ const user = await getCurrentUser() return ( - <div className="flex flex-col min-h-screen"> - <div className="mx-32 my-6 flex-1 md:grid md:grid-cols-[220px_1fr] md:gap-6 lg:grid-cols-[240px_1fr] lg:gap-10"> + <div className="flex min-h-screen flex-col space-y-6"> + <header className="sticky top-0 z-40 border-b bg-background"> + <div className="container flex h-16 items-center justify-between py-4"> + <MainNav /> + <SearchInput className="p-3 md:w-2/3 2xl:w-1/3" /> + {user && <UserAccountNav + user={{ + name: user?.name, + image: user?.image, + username: user?.username, + }} + />} + {!user && <p className="w-8"></p>} + </div> + </header> + <div className="container grid flex-1 gap-12 md:grid-cols-[200px_1fr]"> <aside className="hidden w-[200px] flex-col md:flex"> - <div className="sticky top-0"> - <DashboardNav items={dashboardConfig.sidebarNav} /> - </div> + <DashboardNav items={dashboardConfig.sidebarNav} /> </aside> <main className="flex w-full flex-1 flex-col overflow-hidden"> {children} diff --git a/app/globals.css b/app/globals.css index af41b8b366f3a5c083e5fccf73d4eb190ee0cd42..cb7efa4d9556ea875c5bced31c08b5d8a75a3a6e 100644 --- a/app/globals.css +++ b/app/globals.css @@ -31,7 +31,7 @@ --primary-foreground: 210 40% 98%; /* Secondary colors for <Button /> */ - --secondary: 210 40% 96.1%; + --secondary: 237 100% 96%; --secondary-foreground: 222.2 47.4% 11.2%; /* Used for accents such as hover effects on <DropdownMenuItem>, <SelectItem>...etc */ diff --git a/components/filter-sort-games.tsx b/components/filter-sort-games.tsx index caa9c363c3aeea948dca644e11642d38da75fc43..9b37f76745994a1456f4e59862a3e389b18124e5 100644 --- a/components/filter-sort-games.tsx +++ b/components/filter-sort-games.tsx @@ -57,10 +57,10 @@ export default function Sort() { } return ( - <Card className="p-6 grid items-start gap-2"> + <Card className="p-6 grid items-start gap-2 bg-secondary"> <h1>Filter</h1> <Select value={selectedCategory ? selectedCategory : undefined} key={selectedCategory[0]} onValueChange={(value) => setSelectedCategory(value)}> - <SelectTrigger className={`w-full ${selectedCategory[0] ? 'font-extrabold' : ''}`}> + <SelectTrigger className={`bg-background border-none w-full ${selectedCategory[0] ? 'font-extrabold' : ''}`}> <SelectValue placeholder="By category..." /> </SelectTrigger> <SelectContent> @@ -77,7 +77,7 @@ export default function Sort() { </Select> <Select value={selectedGenre ? selectedGenre : undefined} key={selectedGenre[0]} onValueChange={(value) => setSelectedGenre(value)}> - <SelectTrigger className={`w-full ${selectedGenre[0] ? 'font-extrabold' : ''}`}> + <SelectTrigger className={`bg-background border-none w-full ${selectedGenre[0] ? 'font-extrabold' : ''}`}> <SelectValue placeholder="By genre..." /> </SelectTrigger> <SelectContent> @@ -109,7 +109,7 @@ export default function Sort() { </Select> <Select value={selectedPlatform ? selectedPlatform : undefined} key={selectedPlatform[0]} onValueChange={(value) => setSelectedPlatform(value)}> - <SelectTrigger className={`w-full ${selectedPlatform[0] ? 'font-extrabold' : ''}`}> + <SelectTrigger className={`bg-background border-none w-full ${selectedPlatform[0] ? 'font-extrabold' : ''}`}> <SelectValue placeholder="By Platform..." /> </SelectTrigger> <SelectContent> @@ -126,7 +126,7 @@ export default function Sort() { <h1 className="pt-6">Sort by</h1> <div className="flex space-x-2 pb-1"> <Select value={selectedSortMethod} onValueChange={(value) => setSelectedSortMethod(value)}> - <SelectTrigger className="w-full"> + <SelectTrigger className="bg-background border-none w-full"> <SelectValue placeholder="Rating" /> </SelectTrigger> <SelectContent> @@ -137,7 +137,7 @@ export default function Sort() { </SelectGroup> </SelectContent> </Select> - <Button variant="ghost" onClick={() => toggleSortOrder()}> + <Button variant="ghost" onClick={() => toggleSortOrder()} className="bg-background border-none"> <Icons.arrowdown className={`h-4 w-4 transition-all transform ${selectedSortOrder === 'asc' ? 'rotate-180' : ''}`} /> </Button> </div> diff --git a/components/header.tsx b/components/header.tsx new file mode 100644 index 0000000000000000000000000000000000000000..cdf1c8c5ad8f903e57ed434e5d933c18b21b2512 --- /dev/null +++ b/components/header.tsx @@ -0,0 +1,15 @@ +"use client" + +import Link from "next/link" + +import { GameUnityLogo } from "./logo" + +export function MainNav() { + return ( + <div className="flex gap-6 md:gap-10"> + <Link href="/" className="items-center space-x-2 flex"> + <GameUnityLogo className="h-8 w-8" /> + </Link> + </div> + ) +} \ No newline at end of file diff --git a/components/nav.tsx b/components/nav.tsx index 36b42a3e17aa7e561dbbab3d64715a3c99e43874..a6d6e247390b8102193323cb26041b491133fdfc 100644 --- a/components/nav.tsx +++ b/components/nav.tsx @@ -7,7 +7,6 @@ import { SidebarNavItem } from "@/types"; import { useSession } from "next-auth/react"; import Link from "next/link"; import { usePathname } from "next/navigation"; -import { GameUnityLogo } from "./logo"; import { ModeToggle } from "./mode-toggle"; interface DashboardNavProps { @@ -26,11 +25,6 @@ export default function DashboardNav({ items }: DashboardNavProps) { return ( <nav className="grid items-start gap-2"> - <div className="flex items-center"> - <Link href="/" className={cn("rounded-full p-3 hover:bg-accent")}> - <GameUnityLogo className="h-8 w-8" /> - </Link> - </div> {visibleItems.map((item, index) => { const Icon = Icons[item.icon as keyof IconsType || "arrowRight"]; if (item.title === "My Profile") { @@ -38,7 +32,7 @@ export default function DashboardNav({ items }: DashboardNavProps) { } return ( item.href && ( - <Link key={index} href={item.disabled ? "/" : item.href} className={index == 6 ? "mt-10" : ""}> + <Link key={index} href={item.disabled ? "/" : item.href} className={item.title === "Settings" ? "mt-10" : ""}> <span className={cn( "group flex items-center rounded-md px-3 py-2 font-medium hover:bg-accent hover:text-accent-foreground", diff --git a/components/search-input.tsx b/components/search-input.tsx index 30e96e492fbd0e0caec180b1db0c6d5bc33c9d26..1786c3c50c8ef3dbc2da18cb3ce48cf1913789b3 100644 --- a/components/search-input.tsx +++ b/components/search-input.tsx @@ -6,6 +6,7 @@ import { useState } from 'react'; import { Icons } from './icons'; import { Button } from './ui/button'; import { Input } from './ui/input'; +import { toast } from './ui/use-toast'; interface DocsSearchProps extends React.HTMLAttributes<HTMLFormElement> { } @@ -17,13 +18,20 @@ export default function SearchInput({ className, ...props }: DocsSearchProps) { function onSearch(event: React.FormEvent) { event.preventDefault() - if (!searchQuery) { - router.push(pathname) - return - } + if (pathname === "/games") { + if (!searchQuery) { + router.push(pathname) + return + } - const encoededQuery = encodeURIComponent(searchQuery) - router.push(`${pathname}?search=${encoededQuery}`) // add useSearchParams + const encoededQuery = encodeURIComponent(searchQuery) + router.push(`${pathname}?search=${encoededQuery}`) + } else { + return toast({ + title: "Work in Progress!", + description: "Sorry, but global search is not available yet... ã…¤You can test it out in the games page though!", + }) + } }; return ( diff --git a/components/ui/avatar.tsx b/components/ui/avatar.tsx new file mode 100644 index 0000000000000000000000000000000000000000..51e507ba9d08bcdbb1fb630498f1cbdf2bf50093 --- /dev/null +++ b/components/ui/avatar.tsx @@ -0,0 +1,50 @@ +"use client" + +import * as React from "react" +import * as AvatarPrimitive from "@radix-ui/react-avatar" + +import { cn } from "@/lib/utils" + +const Avatar = React.forwardRef< + React.ElementRef<typeof AvatarPrimitive.Root>, + React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root> +>(({ className, ...props }, ref) => ( + <AvatarPrimitive.Root + ref={ref} + className={cn( + "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", + className + )} + {...props} + /> +)) +Avatar.displayName = AvatarPrimitive.Root.displayName + +const AvatarImage = React.forwardRef< + React.ElementRef<typeof AvatarPrimitive.Image>, + React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image> +>(({ className, ...props }, ref) => ( + <AvatarPrimitive.Image + ref={ref} + className={cn("aspect-square h-full w-full", className)} + {...props} + /> +)) +AvatarImage.displayName = AvatarPrimitive.Image.displayName + +const AvatarFallback = React.forwardRef< + React.ElementRef<typeof AvatarPrimitive.Fallback>, + React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback> +>(({ className, ...props }, ref) => ( + <AvatarPrimitive.Fallback + ref={ref} + className={cn( + "flex h-full w-full items-center justify-center rounded-full bg-muted", + className + )} + {...props} + /> +)) +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/components/user-avatar.tsx b/components/user-avatar.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c627d497b5e18a469c8478332ccc1ddf3d7e0f81 --- /dev/null +++ b/components/user-avatar.tsx @@ -0,0 +1,24 @@ +import { User } from "@prisma/client" +import { AvatarProps } from "@radix-ui/react-avatar" + +import { Icons } from "@/components/icons" +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" + +interface UserAvatarProps extends AvatarProps { + user: Pick<User, "image" | "name"> +} + +export function UserAvatar({ user, ...props }: UserAvatarProps) { + return ( + <Avatar {...props}> + {user.image ? ( + <AvatarImage alt="Picture" src={user.image} /> + ) : ( + <AvatarFallback> + <span className="sr-only">{user.name}</span> + <Icons.user className="h-4 w-4" /> + </AvatarFallback> + )} + </Avatar> + ) +} \ No newline at end of file diff --git a/components/user-nav.tsx b/components/user-nav.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a6e1c0aadf35e6ae863669563cc8180847568336 --- /dev/null +++ b/components/user-nav.tsx @@ -0,0 +1,56 @@ +"use client" + +import { User } from "next-auth" +import { signOut } from "next-auth/react" + +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { UserAvatar } from "@/components/user-avatar" + +type UserUsername = string | null | undefined + +interface UserAccountNavProps extends React.HTMLAttributes<HTMLDivElement> { + user: Pick<User & { username: UserUsername }, "name" | "image" | "username"> +} + +export function UserAccountNav({ user }: UserAccountNavProps) { + return ( + <DropdownMenu> + <DropdownMenuTrigger className="rounded-full"> + <UserAvatar + user={{ name: user.name || null, image: user.image || null }} + className="h-8 w-8" + /> + </DropdownMenuTrigger> + <DropdownMenuContent align="end"> + <div className="flex items-center justify-start gap-2 p-2"> + <div className="flex flex-col space-y-1 leading-none"> + {user.name && <p className="font-medium">{user.name}</p>} + {user.username && ( + <p className="w-[200px] truncate text-sm text-muted-foreground"> + @{user.username} + </p> + )} + </div> + </div> + <DropdownMenuSeparator /> + <DropdownMenuItem + className="cursor-pointer" + onSelect={(event) => { + event.preventDefault() + signOut({ + callbackUrl: `${window.location.origin}/login`, + }) + }} + > + Sign out + </DropdownMenuItem> + </DropdownMenuContent> + </DropdownMenu> + ) +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index fc05649bfd569c114a59a6ecc9fa8beb98752eb1..001892b66f10fbd42c7eaf42112fb808b8855921 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@auth/prisma-adapter": "^1.0.0", "@hookform/resolvers": "^3.1.0", "@prisma/client": "^4.15.0", + "@radix-ui/react-avatar": "^1.0.3", "@radix-ui/react-dropdown-menu": "^2.0.5", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-scroll-area": "^1.0.4", @@ -593,6 +594,32 @@ } } }, + "node_modules/@radix-ui/react-avatar": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.0.3.tgz", + "integrity": "sha512-9ToF7YNex3Ste45LrAeTlKtONI9yVRt/zOS158iilIkW5K/Apeyb/TUQlcEFTEFvWr8Kzdi2ZYrm1/suiXPajQ==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1", + "@radix-ui/react-use-layout-effect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-collection": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.3.tgz", diff --git a/package.json b/package.json index 5372fd5a6a7a1a5aa610cf93e774a15c56239c01..ef91fe612a04aeae7a0bb9cdd7e79cb4098cbd5b 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@auth/prisma-adapter": "^1.0.0", "@hookform/resolvers": "^3.1.0", "@prisma/client": "^4.15.0", + "@radix-ui/react-avatar": "^1.0.3", "@radix-ui/react-dropdown-menu": "^2.0.5", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-scroll-area": "^1.0.4",