From aa8f46dffb1d26f1c73b7e63826336ae05b06289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20Akg=C3=BCl?= <s86116@bht-berlin.de> Date: Tue, 6 Jun 2023 04:35:32 +0200 Subject: [PATCH] header done --- app/(content)/(gaming)/games/page.tsx | 4 -- app/(content)/{ => (user)}/followers/page.tsx | 0 .../{ => (user)}/notifications/page.tsx | 0 app/(content)/layout.tsx | 25 +++++++-- app/globals.css | 2 +- components/filter-sort-games.tsx | 12 ++-- components/header.tsx | 15 +++++ components/nav.tsx | 8 +-- components/search-input.tsx | 20 +++++-- components/ui/avatar.tsx | 50 +++++++++++++++++ components/user-avatar.tsx | 24 ++++++++ components/user-nav.tsx | 56 +++++++++++++++++++ package-lock.json | 27 +++++++++ package.json | 1 + 14 files changed, 215 insertions(+), 29 deletions(-) rename app/(content)/{ => (user)}/followers/page.tsx (100%) rename app/(content)/{ => (user)}/notifications/page.tsx (100%) create mode 100644 components/header.tsx create mode 100644 components/ui/avatar.tsx create mode 100644 components/user-avatar.tsx create mode 100644 components/user-nav.tsx diff --git a/app/(content)/(gaming)/games/page.tsx b/app/(content)/(gaming)/games/page.tsx index 4ff601b..81b9172 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 8a44982..3c53c0a 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 af41b8b..cb7efa4 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 caa9c36..9b37f76 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 0000000..cdf1c8c --- /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 36b42a3..a6d6e24 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 30e96e4..1786c3c 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 0000000..51e507b --- /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 0000000..c627d49 --- /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 0000000..a6e1c0a --- /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 fc05649..001892b 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 5372fd5..ef91fe6 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", -- GitLab