diff --git a/public/images/d2jam.png b/public/images/d2jam.png new file mode 100644 index 0000000..1a75162 Binary files /dev/null and b/public/images/d2jam.png differ diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx new file mode 100644 index 0000000..23c68bc --- /dev/null +++ b/src/app/about/page.tsx @@ -0,0 +1,3 @@ +export default function AboutPage() { + return <p>About page coming soon</p>; +} diff --git a/src/app/games/page.tsx b/src/app/games/page.tsx new file mode 100644 index 0000000..3e3a60e --- /dev/null +++ b/src/app/games/page.tsx @@ -0,0 +1,3 @@ +export default function GamesPage() { + return <p>Games page coming soon</p>; +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 274dd35..dedb14f 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -4,6 +4,8 @@ import "./globals.css"; import Navbar from "../components/navbar"; import Providers from "./providers"; import { ToastContainer } from "react-toastify"; +import { Spacer } from "@nextui-org/react"; +import Footer from "@/components/footer"; const inter = Inter({ subsets: ["latin"] }); @@ -22,9 +24,13 @@ export default function RootLayout({ <body className={inter.className}> <Providers> <div className="dark"> - <div className="bg-zinc-100 dark:bg-zinc-950 min-h-screen"> + <div className="bg-gradient-to-br from-[#181818] to-[#222] min-h-screen flex flex-col"> <Navbar /> - <div className="max-w-8xl mx-auto">{children}</div> + <Spacer y={5} /> + <div className="max-w-8xl mx-auto flex-grow w-full"> + {children} + </div> + <Footer /> <ToastContainer /> </div> </div> diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 15a9106..4ab9cfe 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -48,6 +48,7 @@ export default function UserPage() { body: JSON.stringify({ username: username, password: password }), method: "POST", headers: { "Content-Type": "application/json" }, + credentials: "include", } ); @@ -57,7 +58,15 @@ export default function UserPage() { return; } - const { token, user } = await response.json(); + const { user } = await response.json(); + const token = response.headers.get("Authorization"); + console.log(response.headers); + + if (!token) { + toast.error("Failed to retreive access token"); + setPassword(""); + return; + } document.cookie = `token=${token}`; document.cookie = `user=${user.slug}`; diff --git a/src/app/page.tsx b/src/app/page.tsx index fc90ece..6873197 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,46 +1,16 @@ -import { Image } from "@nextui-org/image"; -import { Button } from "@nextui-org/button"; -import { SiDiscord } from "@icons-pack/react-simple-icons"; -import { Link } from "@nextui-org/react"; import Posts from "@/components/posts"; import Timers from "@/components/timers"; +import JamHeader from "@/components/jam-header"; export default async function Home() { return ( - <div className="w-full"> - <div className="fixed left-0 top-0 w-full h-full z-0"> - <Image - src="/images/bg.jpg" - alt="Home background" - className="object-cover w-full h-full" - radius="none" - loading="eager" - removeWrapper - /> - <div className="absolute left-0 top-0 w-full h-full bg-gradient-to-r from-black/50 to-transparent z-10" /> + <div className="flex justify-between flex-wrap"> + <div className="md:w-2/3"> + <JamHeader /> + <Posts /> </div> - <div className="z-10 relative flex w-full flex-wrap"> - <div> - <div className="flex flex-col gap-4 py-4 sm:py-8 md:py-12 pl-16"> - <h1 className="text-3xl sm:text-4xl md:text-5xl">Dare2Jam</h1> - <p className="text-lg sm:text-xl">April 4th - 7th</p> - <div className="flex gap-2"> - <Link href="https://discord.gg/rfmKzM6ASw" target="_blank"> - <Button - variant="bordered" - className="border-white/50 text-white" - startContent={<SiDiscord />} - > - Join the Discord - </Button> - </Link> - </div> - </div> - <Posts /> - </div> - <div className="w-1/3 flex justify-end py-4 sm:py-8 md:py-12 flex-grow"> - <Timers /> - </div> + <div> + <Timers /> </div> </div> ); diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx index 58f4a08..72a84c0 100644 --- a/src/app/settings/page.tsx +++ b/src/app/settings/page.tsx @@ -28,6 +28,7 @@ export default function UserPage() { : `http://localhost:3005/api/v1/self?username=${getCookie("user")}`, { headers: { authorization: `Bearer ${getCookie("token")}` }, + credentials: "include", } ); diff --git a/src/app/signup/page.tsx b/src/app/signup/page.tsx index d108b89..3ad456f 100644 --- a/src/app/signup/page.tsx +++ b/src/app/signup/page.tsx @@ -1,9 +1,7 @@ "use client"; import { Button, Form, Input } from "@nextui-org/react"; -import { redirect } from "next/navigation"; import { useState } from "react"; -import { toast } from "react-toastify"; export default function UserPage() { const [username, setUsername] = useState(""); @@ -83,13 +81,13 @@ export default function UserPage() { return; } - const { token, user } = await response.json(); - document.cookie = `token=${token}`; - document.cookie = `user=${user.slug}`; + // const { token, user } = await response.json(); + // document.cookie = `token=${token}`; + // document.cookie = `user=${user.slug}`; - toast.success("Successfully signed up"); + // toast.success("Successfully signed up"); - redirect("/"); + // redirect("/"); }} > <Input diff --git a/src/app/u/[slug]/page.tsx b/src/app/u/[slug]/page.tsx index 9a86a8b..2c91b06 100644 --- a/src/app/u/[slug]/page.tsx +++ b/src/app/u/[slug]/page.tsx @@ -1,9 +1,34 @@ -import User from "../../../components/user"; +"use client"; + +import { UserType } from "@/types/UserType"; +import { useParams } from "next/navigation"; +import { useEffect, useState } from "react"; export default function UserPage() { + const [user, setUser] = useState<UserType>(); + const { slug } = useParams(); + + useEffect(() => { + const fetchUser = async () => { + const response = await fetch( + process.env.NEXT_PUBLIC_MODE === "PROD" + ? `https://d2jam.com/api/v1/user?slug=${slug}` + : `http://localhost:3005/api/v1/user?slug=${slug}` + ); + setUser(await response.json()); + }; + + fetchUser(); + }, [slug]); + return ( <div> - <User /> + {user && ( + <div> + <p>{user.name}</p> + </div> + )} </div> ); + return <div></div>; } diff --git a/src/components/announcements/index.tsx b/src/components/announcements/index.tsx deleted file mode 100644 index f48dd64..0000000 --- a/src/components/announcements/index.tsx +++ /dev/null @@ -1,8 +0,0 @@ -"use client"; - -export default function Announcements() { - return ( - <div></div> - ); - } - \ No newline at end of file diff --git a/src/components/footer/index.tsx b/src/components/footer/index.tsx new file mode 100644 index 0000000..b433acc --- /dev/null +++ b/src/components/footer/index.tsx @@ -0,0 +1,20 @@ +import IconLink from "../link-components/IconLink"; +import { SiDiscord, SiForgejo, SiGithub } from "@icons-pack/react-simple-icons"; + +export default function Footer() { + return ( + <div className="p-8 bg-[#222] mt-8 border-t-2 border-white/15"> + <div className="flex justify-between"> + <div></div> + <div className="flex gap-3"> + <IconLink icon={<SiGithub />} href="https://github.com/Dare2Jam" /> + <IconLink + icon={<SiForgejo />} + href="https://git.edikoyo.com/Ategon/Jamjar" + /> + <IconLink icon={<SiDiscord />} href="https://discord.d2jam.com" /> + </div> + </div> + </div> + ); +} diff --git a/src/components/jam-header/index.tsx b/src/components/jam-header/index.tsx new file mode 100644 index 0000000..9bd6942 --- /dev/null +++ b/src/components/jam-header/index.tsx @@ -0,0 +1,15 @@ +import { Calendar } from "lucide-react"; + +export default function JamHeader() { + return ( + <div className="bg-[#124a88] flex rounded-2xl overflow-hidden"> + <div className="bg-[#1892b3] p-4 px-6 flex items-center gap-2 font-bold"> + <Calendar /> + <p>Dare2Jam 1</p> + </div> + <div className="p-4 px-6 font-bold"> + <p>April 4th - 7th</p> + </div> + </div> + ); +} diff --git a/src/components/link-components/ButtonAction.tsx b/src/components/link-components/ButtonAction.tsx new file mode 100644 index 0000000..34581cf --- /dev/null +++ b/src/components/link-components/ButtonAction.tsx @@ -0,0 +1,25 @@ +import { Button } from "@nextui-org/react"; +import { ReactNode } from "react"; + +interface ButtonActionProps { + icon?: ReactNode; + onPress: () => void; + name: string; +} + +export default function ButtonAction({ + icon, + onPress, + name, +}: ButtonActionProps) { + return ( + <Button + endContent={icon} + className="text-white border-white/50 hover:scale-110 transition-all transform duration-500 ease-in-out" + variant="bordered" + onPress={onPress} + > + {name} + </Button> + ); +} diff --git a/src/components/link-components/ButtonLink.tsx b/src/components/link-components/ButtonLink.tsx new file mode 100644 index 0000000..fdfc3f3 --- /dev/null +++ b/src/components/link-components/ButtonLink.tsx @@ -0,0 +1,25 @@ +import { Button, Link } from "@nextui-org/react"; +import { ReactNode } from "react"; + +interface ButtonLinkProps { + icon?: ReactNode; + href: string; + name: string; +} + +export default function ButtonLink({ icon, href, name }: ButtonLinkProps) { + return ( + <Link + href={href} + className="text-white flex justify-center duration-500 ease-in-out transition-all transform hover:scale-110 ease-in-out" + > + <Button + endContent={icon} + className="text-white border-white/50 transition-all transform duration-500 ease-in-out" + variant="bordered" + > + {name} + </Button> + </Link> + ); +} diff --git a/src/components/link-components/IconLink.tsx b/src/components/link-components/IconLink.tsx new file mode 100644 index 0000000..c09e3f2 --- /dev/null +++ b/src/components/link-components/IconLink.tsx @@ -0,0 +1,18 @@ +import { Link } from "@nextui-org/react"; +import { ReactNode } from "react"; + +interface IconLinkProps { + icon: ReactNode; + href: string; +} + +export default function IconLink({ icon, href }: IconLinkProps) { + return ( + <Link + href={href} + className="text-white flex justify-center duration-500 ease-in-out transition-all transform hover:scale-125" + > + {icon} + </Link> + ); +} diff --git a/src/components/link-components/Link.tsx b/src/components/link-components/Link.tsx new file mode 100644 index 0000000..c93bc58 --- /dev/null +++ b/src/components/link-components/Link.tsx @@ -0,0 +1,17 @@ +import { Link as BaseLink } from "@nextui-org/react"; + +interface LinkProps { + name: string; + href: string; +} + +export default function Link({ name, href }: LinkProps) { + return ( + <BaseLink + href={href} + className="text-white flex justify-center duration-500 ease-in-out transition-all transform hover:scale-110" + > + {name} + </BaseLink> + ); +} diff --git a/src/components/navbar/MobileNavbar.tsx b/src/components/navbar/MobileNavbar.tsx new file mode 100644 index 0000000..b500fe8 --- /dev/null +++ b/src/components/navbar/MobileNavbar.tsx @@ -0,0 +1,111 @@ +"use client"; + +import { + Image, + Link, + NavbarBrand, + Navbar as NavbarBase, + NavbarContent, +} from "@nextui-org/react"; +import NavbarButtonLink from "./NavbarButtonLink"; +import { LogInIcon, NotebookPen } from "lucide-react"; +import NextImage from "next/image"; +import { useEffect, useState } from "react"; +import { usePathname } from "next/navigation"; +import { getCookie, hasCookie } from "@/helpers/cookie"; +import { getCurrentJam } from "@/helpers/jam"; +import { JamType } from "@/types/JamType"; +import { UserType } from "@/types/UserType"; +import MobileNavbarUser from "./MobileNavbarUser"; + +export default function MobileNavbar() { + const pathname = usePathname(); + const [jam, setJam] = useState<JamType | null>(); + const [isInJam, setIsInJam] = useState<boolean>(); + const [user, setUser] = useState<UserType>(); + + useEffect(() => { + loadUser(); + async function loadUser() { + const currentJam = await getCurrentJam(); + setJam(currentJam); + + if (!hasCookie("token")) { + setUser(undefined); + return; + } + + const response = await fetch( + process.env.NEXT_PUBLIC_MODE === "PROD" + ? `https://d2jam.com/api/v1/self?username=${getCookie("user")}` + : `http://localhost:3005/api/v1/self?username=${getCookie("user")}`, + { + headers: { authorization: `Bearer ${getCookie("token")}` }, + credentials: "include", + } + ); + + const user = await response.json(); + + if ( + currentJam && + user.jams.filter((jam: JamType) => jam.id == currentJam.id).length > 0 + ) { + setIsInJam(true); + } else { + setIsInJam(false); + } + + if (response.status == 200) { + setUser(user); + } else { + setUser(undefined); + } + } + }, [pathname]); + + return ( + <NavbarBase maxWidth="2xl" className="bg-[#222] p-1" isBordered height={80}> + {/* Left side navbar items */} + <NavbarContent justify="start" className="gap-10"> + <NavbarBrand className="flex-grow-0"> + <Link + href="/" + className="duration-500 ease-in-out transition-all transform hover:scale-110" + > + <Image + as={NextImage} + src="/images/d2jam.png" + className="min-w-[70px]" + alt="Dare2Jam logo" + width={70} + height={59.7} + /> + </Link> + </NavbarBrand> + </NavbarContent> + + {/* Right side navbar items */} + <NavbarContent justify="end" className="gap-4"> + {!user && ( + <NavbarButtonLink icon={<LogInIcon />} name="Log In" href="/login" /> + )} + {!user && ( + <NavbarButtonLink + icon={<NotebookPen />} + name="Sign Up" + href="/signup" + /> + )} + {user && ( + <MobileNavbarUser + user={user} + isInJam={isInJam} + setIsInJam={setIsInJam} + jam={jam} + /> + )} + </NavbarContent> + </NavbarBase> + ); +} diff --git a/src/components/navbar/MobileNavbarUser.tsx b/src/components/navbar/MobileNavbarUser.tsx new file mode 100644 index 0000000..1ab542a --- /dev/null +++ b/src/components/navbar/MobileNavbarUser.tsx @@ -0,0 +1,102 @@ +import { + Avatar, + Dropdown, + DropdownItem, + DropdownMenu, + DropdownTrigger, + NavbarItem, +} from "@nextui-org/react"; +import { UserType } from "@/types/UserType"; +import { getCurrentJam, joinJam } from "@/helpers/jam"; +import { JamType } from "@/types/JamType"; +import { Dispatch, SetStateAction } from "react"; +import { toast } from "react-toastify"; + +interface NavbarUserProps { + user: UserType; + jam?: JamType | null; + setIsInJam: Dispatch<SetStateAction<boolean | undefined>>; + isInJam?: boolean; +} + +export default function MobileNavbarUser({ + user, + jam, + setIsInJam, + isInJam, +}: NavbarUserProps) { + return ( + <NavbarItem> + <Dropdown> + <DropdownTrigger> + <Avatar src={user.profilePicture} className="cursor-pointer" /> + </DropdownTrigger> + <DropdownMenu> + {jam && isInJam ? ( + <DropdownItem + key="create-game" + href="/create-game" + className="text-black" + > + Create Game + </DropdownItem> + ) : null} + {jam && !isInJam ? ( + <DropdownItem + key="join-event" + className="text-black" + onPress={async () => { + try { + const currentJam = await getCurrentJam(); + + if (!currentJam) { + toast.error("There is no jam to join"); + return; + } + + if (await joinJam(currentJam.id)) { + setIsInJam(true); + } + } catch (error) { + console.error("Error during join process:", error); + } + }} + > + Join Event + </DropdownItem> + ) : null} + <DropdownItem + key="create-post" + href="/create-post" + className="text-black" + > + Create Post + </DropdownItem> + <DropdownItem + key="profile" + className="text-black" + href={`/u/${user.slug}`} + > + Profile + </DropdownItem> + <DropdownItem + showDivider + key="settings" + className="text-black" + href="/settings" + > + Settings + </DropdownItem> + <DropdownItem + key="logout" + color="danger" + className="text-danger" + href="/logout" + > + Logout + </DropdownItem> + </DropdownMenu> + </Dropdown> + </NavbarItem> + ); +} diff --git a/src/components/navbar/NavbarButtonAction.tsx b/src/components/navbar/NavbarButtonAction.tsx new file mode 100644 index 0000000..82d171c --- /dev/null +++ b/src/components/navbar/NavbarButtonAction.tsx @@ -0,0 +1,21 @@ +import { NavbarItem } from "@nextui-org/react"; +import { ReactNode } from "react"; +import ButtonAction from "../link-components/ButtonAction"; + +interface NavbarButtonActionProps { + icon?: ReactNode; + onPress: () => void; + name: string; +} + +export default function NavbarButtonAction({ + icon, + onPress, + name, +}: NavbarButtonActionProps) { + return ( + <NavbarItem> + <ButtonAction icon={icon} onPress={onPress} name={name} /> + </NavbarItem> + ); +} diff --git a/src/components/navbar/NavbarButtonLink.tsx b/src/components/navbar/NavbarButtonLink.tsx new file mode 100644 index 0000000..8bfe20a --- /dev/null +++ b/src/components/navbar/NavbarButtonLink.tsx @@ -0,0 +1,21 @@ +import { NavbarItem } from "@nextui-org/react"; +import { ReactNode } from "react"; +import ButtonLink from "../link-components/ButtonLink"; + +interface NavbarButtonLinkProps { + icon?: ReactNode; + href: string; + name: string; +} + +export default function NavbarButtonLink({ + icon, + href, + name, +}: NavbarButtonLinkProps) { + return ( + <NavbarItem> + <ButtonLink icon={icon} href={href} name={name} /> + </NavbarItem> + ); +} diff --git a/src/components/navbar/NavbarIconLink.tsx b/src/components/navbar/NavbarIconLink.tsx new file mode 100644 index 0000000..ec4fdca --- /dev/null +++ b/src/components/navbar/NavbarIconLink.tsx @@ -0,0 +1,16 @@ +import { NavbarItem } from "@nextui-org/react"; +import { ReactNode } from "react"; +import IconLink from "../link-components/IconLink"; + +interface NavbarIconLinkProps { + icon: ReactNode; + href: string; +} + +export default function NavbarIconLink({ icon, href }: NavbarIconLinkProps) { + return ( + <NavbarItem> + <IconLink icon={icon} href={href} /> + </NavbarItem> + ); +} diff --git a/src/components/navbar/NavbarLink.tsx b/src/components/navbar/NavbarLink.tsx new file mode 100644 index 0000000..c64e1c2 --- /dev/null +++ b/src/components/navbar/NavbarLink.tsx @@ -0,0 +1,15 @@ +import { NavbarItem } from "@nextui-org/react"; +import Link from "../link-components/Link"; + +interface NavbarLinkProps { + name: string; + href: string; +} + +export default function NavbarLink({ name, href }: NavbarLinkProps) { + return ( + <NavbarItem> + <Link name={name} href={href} /> + </NavbarItem> + ); +} diff --git a/src/components/navbar/NavbarSearchbar.tsx b/src/components/navbar/NavbarSearchbar.tsx new file mode 100644 index 0000000..593d97d --- /dev/null +++ b/src/components/navbar/NavbarSearchbar.tsx @@ -0,0 +1,9 @@ +import { Input, NavbarItem } from "@nextui-org/react"; + +export default function NavbarSearchbar() { + return ( + <NavbarItem> + <Input placeholder="Search" /> + </NavbarItem> + ); +} diff --git a/src/components/navbar/PCNavbar.tsx b/src/components/navbar/PCNavbar.tsx new file mode 100644 index 0000000..6cc5965 --- /dev/null +++ b/src/components/navbar/PCNavbar.tsx @@ -0,0 +1,152 @@ +"use client"; + +import { + Image, + Link, + NavbarBrand, + Navbar as NavbarBase, + NavbarContent, + Divider, +} from "@nextui-org/react"; +import NavbarLink from "./NavbarLink"; +import NavbarSearchbar from "./NavbarSearchbar"; +import NavbarButtonLink from "./NavbarButtonLink"; +import { + CalendarPlus, + Gamepad2, + LogInIcon, + NotebookPen, + SquarePen, +} from "lucide-react"; +import NextImage from "next/image"; +import { useEffect, useState } from "react"; +import { usePathname } from "next/navigation"; +import { getCookie, hasCookie } from "@/helpers/cookie"; +import { getCurrentJam, joinJam } from "@/helpers/jam"; +import { JamType } from "@/types/JamType"; +import { UserType } from "@/types/UserType"; +import NavbarUser from "./PCNavbarUser"; +import NavbarButtonAction from "./NavbarButtonAction"; +import { toast } from "react-toastify"; + +export default function PCNavbar() { + const pathname = usePathname(); + const [jam, setJam] = useState<JamType | null>(); + const [isInJam, setIsInJam] = useState<boolean>(); + const [user, setUser] = useState<UserType>(); + + useEffect(() => { + loadUser(); + async function loadUser() { + const currentJam = await getCurrentJam(); + setJam(currentJam); + + if (!hasCookie("token")) { + setUser(undefined); + return; + } + + const response = await fetch( + process.env.NEXT_PUBLIC_MODE === "PROD" + ? `https://d2jam.com/api/v1/self?username=${getCookie("user")}` + : `http://localhost:3005/api/v1/self?username=${getCookie("user")}`, + { + headers: { authorization: `Bearer ${getCookie("token")}` }, + credentials: "include", + } + ); + + const user = await response.json(); + + if ( + currentJam && + user.jams.filter((jam: JamType) => jam.id == currentJam.id).length > 0 + ) { + setIsInJam(true); + } else { + setIsInJam(false); + } + + if (response.status == 200) { + setUser(user); + } else { + setUser(undefined); + } + } + }, [pathname]); + + return ( + <NavbarBase maxWidth="2xl" className="bg-[#222] p-1" isBordered height={80}> + {/* Left side navbar items */} + <NavbarContent justify="start" className="gap-10"> + <NavbarBrand className="flex-grow-0"> + <Link + href="/" + className="duration-500 ease-in-out transition-all transform hover:scale-110" + > + <Image + as={NextImage} + src="/images/d2jam.png" + className="min-w-[70px]" + alt="Dare2Jam logo" + width={70} + height={59.7} + /> + </Link> + </NavbarBrand> + + <NavbarLink href="/about" name="About" /> + <NavbarLink href="/games" name="Games" /> + </NavbarContent> + + {/* Right side navbar items */} + <NavbarContent justify="end" className="gap-4"> + <NavbarSearchbar /> + {user && <Divider orientation="vertical" className="h-1/2" />} + {user && jam && isInJam && ( + <NavbarButtonLink + icon={<Gamepad2 />} + name="Create Game" + href="/create-game" + /> + )} + {user && jam && !isInJam && ( + <NavbarButtonAction + icon={<CalendarPlus />} + name="Join jam" + onPress={async () => { + const currentJam = await getCurrentJam(); + + if (!currentJam) { + toast.error("There is no jam to join"); + return; + } + if (await joinJam(currentJam.id)) { + setIsInJam(true); + } + }} + /> + )} + {user && ( + <NavbarButtonLink + icon={<SquarePen />} + name="Create Post" + href="/create-post" + /> + )} + <Divider orientation="vertical" className="h-1/2" /> + {!user && ( + <NavbarButtonLink icon={<LogInIcon />} name="Log In" href="/login" /> + )} + {!user && ( + <NavbarButtonLink + icon={<NotebookPen />} + name="Sign Up" + href="/signup" + /> + )} + {user && <NavbarUser user={user} />} + </NavbarContent> + </NavbarBase> + ); +} diff --git a/src/components/navbar/PCNavbarUser.tsx b/src/components/navbar/PCNavbarUser.tsx new file mode 100644 index 0000000..c6a60b7 --- /dev/null +++ b/src/components/navbar/PCNavbarUser.tsx @@ -0,0 +1,50 @@ +import { + Avatar, + Dropdown, + DropdownItem, + DropdownMenu, + DropdownTrigger, + NavbarItem, +} from "@nextui-org/react"; +import { UserType } from "@/types/UserType"; + +interface NavbarUserProps { + user: UserType; +} + +export default function PCNavbarUser({ user }: NavbarUserProps) { + return ( + <NavbarItem> + <Dropdown> + <DropdownTrigger> + <Avatar src={user.profilePicture} className="cursor-pointer" /> + </DropdownTrigger> + <DropdownMenu> + <DropdownItem + key="profile" + className="text-black" + href={`/u/${user.slug}`} + > + Profile + </DropdownItem> + <DropdownItem + showDivider + key="settings" + className="text-black" + href="/settings" + > + Settings + </DropdownItem> + <DropdownItem + key="logout" + color="danger" + className="text-danger" + href="/logout" + > + Logout + </DropdownItem> + </DropdownMenu> + </Dropdown> + </NavbarItem> + ); +} diff --git a/src/components/navbar/index.tsx b/src/components/navbar/index.tsx index 333d8fb..4408691 100644 --- a/src/components/navbar/index.tsx +++ b/src/components/navbar/index.tsx @@ -1,531 +1,20 @@ "use client"; -import { - Navbar as NavbarBase, - NavbarBrand, - NavbarContent, - NavbarItem, -} from "@nextui-org/navbar"; -import { Link } from "@nextui-org/link"; -import { Divider } from "@nextui-org/divider"; -import { - Avatar, - Button, - Dropdown, - DropdownItem, - DropdownMenu, - DropdownTrigger, - Image, - Tooltip, -} from "@nextui-org/react"; -import { SiDiscord, SiForgejo, SiGithub } from "@icons-pack/react-simple-icons"; -import { - CalendarPlus, - Gamepad2, - LogInIcon, - Menu, - NotebookPen, - SquarePen, -} from "lucide-react"; import { useEffect, useState } from "react"; -import { hasCookie, getCookie } from "@/helpers/cookie"; -import { usePathname } from "next/navigation"; -import { UserType } from "@/types/UserType"; -import { getCurrentJam, joinJam } from "@/helpers/jam"; -import { toast } from "react-toastify"; -import { JamType } from "@/types/JamType"; +import PCNavbar from "./PCNavbar"; +import MobileNavbar from "./MobileNavbar"; export default function Navbar() { - const [user, setUser] = useState<UserType>(); - const pathname = usePathname(); - const [isMobile, setIsMobile] = useState(false); - const [jam, setJam] = useState<JamType | null>(); - const [isInJam, setIsInJam] = useState<boolean>(); - - useEffect(() => { - loadUser(); - async function loadUser() { - const currentJam = await getCurrentJam(); - setJam(currentJam); - - if (!hasCookie("token")) { - setUser(undefined); - return; - } - - const response = await fetch( - process.env.NEXT_PUBLIC_MODE === "PROD" - ? `https://d2jam.com/api/v1/self?username=${getCookie("user")}` - : `http://localhost:3005/api/v1/self?username=${getCookie("user")}`, - { - headers: { authorization: `Bearer ${getCookie("token")}` }, - } - ); - - const user = await response.json(); - - if ( - currentJam && - user.jams.filter((jam: JamType) => jam.id == currentJam.id).length > 0 - ) { - setIsInJam(true); - } else { - setIsInJam(false); - } - - if (response.status == 200) { - setUser(user); - } else { - setUser(undefined); - } - } - }, [pathname]); + const [isMobile, setIsMobile] = useState<boolean>(false); useEffect(() => { const handleResize = () => { - setIsMobile(window.innerWidth <= 768); // Adjust breakpoint as needed + setIsMobile(window.innerWidth <= 768); }; handleResize(); window.addEventListener("resize", handleResize); return () => window.removeEventListener("resize", handleResize); }, []); - return ( - <NavbarBase - shouldHideOnScroll - maxWidth="2xl" - className="bg-transparent p-1" - > - <NavbarBrand> - <Link - href="/" - className="duration-500 ease-in-out transition-all transform hover:scale-110" - > - <Image src="/images/dare2jam.png" alt="Dare2Jam logo" width={80} /> - </Link> - </NavbarBrand> - <NavbarContent justify="end"> - {isMobile ? ( - user ? ( - <Dropdown> - <DropdownTrigger> - <Avatar src={user.profilePicture} /> - </DropdownTrigger> - <DropdownMenu className="text-black"> - {jam && isInJam ? ( - <DropdownItem key="create-game" href="/create-game"> - Create Game - </DropdownItem> - ) : null} - {jam && !isInJam ? ( - <DropdownItem - key="join-event" - onPress={async () => { - try { - const currentJam = await getCurrentJam(); - - if (!currentJam) { - toast.error("There is no jam to join"); - return; - } - - if (await joinJam(currentJam.id)) { - setIsInJam(true); - } - } catch (error) { - console.error("Error during join process:", error); - } - }} - > - Join Event - </DropdownItem> - ) : null} - <DropdownItem key="create-post" href="/create-post"> - Create Post - </DropdownItem> - <DropdownItem - key="profile" - className="text-black" - href={`/u/${user.slug}`} - > - Profile - </DropdownItem> - <DropdownItem - showDivider - key="settings" - className="text-black" - href="/settings" - > - Settings - </DropdownItem> - <DropdownItem - key="github" - href="https://github.com/Ategon/Jamjar" - > - GitHub - </DropdownItem> - <DropdownItem - key="forgejo" - href="https://git.edikoyo.com/Ategon/Jamjar" - > - Forgejo - </DropdownItem> - <DropdownItem - key="discord" - href="https://discord.gg/rfmKzM6ASw" - > - Discord - </DropdownItem> - <DropdownItem key="logout" color="danger" href="/logout"> - Logout - </DropdownItem> - </DropdownMenu> - </Dropdown> - ) : ( - <Dropdown> - <DropdownTrigger> - <Menu /> - </DropdownTrigger> - <DropdownMenu className="text-black"> - <DropdownItem - key="github" - href="https://github.com/Ategon/Jamjar" - > - GitHub - </DropdownItem> - <DropdownItem - key="forgejo" - href="https://git.edikoyo.com/Ategon/Jamjar" - > - Forgejo - </DropdownItem> - <DropdownItem - key="discord" - href="https://discord.gg/rfmKzM6ASw" - > - Discord - </DropdownItem> - <DropdownItem key="login" href="/login"> - Log In - </DropdownItem> - <DropdownItem key="signup" href="/signup"> - Sign Up - </DropdownItem> - </DropdownMenu> - </Dropdown> - ) - ) : ( - <div className="flex gap-3 items-center"> - {user && jam && isInJam && ( - <NavbarItem> - <Link href="/create-game"> - <Button - endContent={<Gamepad2 />} - className="text-white border-white/50 hover:border-green-100/50 hover:text-green-100 hover:scale-110 transition-all transform duration-500 ease-in-out" - variant="bordered" - > - Create Game - </Button> - </Link> - </NavbarItem> - )} - {user && jam && !isInJam && ( - <NavbarItem> - <Button - endContent={<CalendarPlus />} - className="text-white border-white/50 hover:border-green-100/50 hover:text-green-100 hover:scale-110 transition-all transform duration-500 ease-in-out" - variant="bordered" - onPress={async () => { - const currentJam = await getCurrentJam(); - - if (!currentJam) { - toast.error("There is no jam to join"); - return; - } - if (await joinJam(currentJam.id)) { - setIsInJam(true); - } - }} - > - Join Jam - </Button> - </NavbarItem> - )} - {user && ( - <NavbarItem className="flex items-center"> - <Link href="/create-post"> - <Button - endContent={<SquarePen />} - className="text-white border-white/50 hover:border-green-100/50 hover:text-green-100 hover:scale-110 transition-all transform duration-500 ease-in-out" - variant="bordered" - > - Create Post - </Button> - </Link> - </NavbarItem> - )} - <NavbarItem> - <Tooltip - delay={1000} - content={ - <div className="px-1 py-2 text-black text-center"> - <div className="text-small font-bold">GitHub</div> - <div className="text-tiny">Source Code</div> - </div> - } - > - <Link - href="https://github.com/Dare2Jam/" - className="text-white flex justify-center duration-500 ease-in-out transition-all transform hover:scale-125 hover:text-red-100" - isExternal - > - <SiGithub title="" /> - </Link> - </Tooltip> - </NavbarItem> - <NavbarItem> - <Tooltip - delay={1000} - content={ - <div className="px-1 py-2 text-black text-center"> - <div className="text-small font-bold">Forgejo</div> - <div className="text-tiny">Source Code</div> - </div> - } - > - <Link - href="https://git.edikoyo.com/Ategon/Jamjar" - className="text-white flex justify-center duration-500 ease-in-out transition-all transform hover:scale-125 hover:text-red-100" - isExternal - > - <SiForgejo title="" /> - </Link> - </Tooltip> - </NavbarItem> - <NavbarItem> - <Link - href="https://discord.gg/rfmKzM6ASw" - className="text-white flex justify-center duration-500 ease-in-out transition-all transform hover:scale-125 hover:text-indigo-100" - isExternal - > - <SiDiscord /> - </Link> - </NavbarItem> - <Divider orientation="vertical" className="h-1/2" /> - {!user ? ( - <div className="flex gap-3 items-center"> - <NavbarItem> - <Link href="/login"> - <Button - endContent={<LogInIcon />} - className="text-white border-white/50 hover:border-green-100/50 hover:text-green-100 hover:scale-110 transition-all transform duration-500 ease-in-out" - variant="bordered" - > - Log In - </Button> - </Link> - </NavbarItem> - <NavbarItem> - <Link href="/signup"> - <Button - endContent={<NotebookPen />} - className="text-white border-white/50 hover:border-green-100/50 hover:text-green-100 hover:scale-110 transition-all transform duration-500 ease-in-out" - variant="bordered" - > - Sign up - </Button> - </Link> - </NavbarItem> - </div> - ) : ( - <Dropdown> - <DropdownTrigger> - <Avatar src={user.profilePicture} /> - </DropdownTrigger> - <DropdownMenu> - <DropdownItem - key="profile" - className="text-black" - href={`/u/${user.slug}`} - > - Profile - </DropdownItem> - <DropdownItem - showDivider - key="settings" - className="text-black" - href="/settings" - > - Settings - </DropdownItem> - <DropdownItem - key="logout" - color="danger" - className="text-danger" - href="/logout" - > - Logout - </DropdownItem> - </DropdownMenu> - </Dropdown> - )} - </div> - )} - </NavbarContent> - </NavbarBase> - ); + return isMobile ? <MobileNavbar /> : <PCNavbar />; } -/* - -{isMobile ? ( - // Mobile view - user ? ( - <Dropdown> - <DropdownTrigger> - <Avatar src={user.profilePicture} /> - </DropdownTrigger> - <DropdownMenu> - <DropdownItem key="create-post" href="/create-post"> - Create Post - </DropdownItem> - <DropdownItem key="logout" color="danger" href="/logout"> - Logout - </DropdownItem> - </DropdownMenu> - </Dropdown> - ) : ( - <Dropdown> - <DropdownTrigger> - <Button auto flat className="text-white"> - ☰ - </Button> - </DropdownTrigger> - <DropdownMenu> - <DropdownItem key="github" href="https://github.com/Ategon/Jamjar" isExternal> - GitHub - </DropdownItem> - <DropdownItem key="forgejo" href="https://git.edikoyo.com/Ategon/Jamjar" isExternal> - Forgejo - </DropdownItem> - <DropdownItem key="discord" href="https://discord.gg/rfmKzM6ASw" isExternal> - Discord - </DropdownItem> - <DropdownItem key="login" href="/login"> - Log In - </DropdownItem> - <DropdownItem key="signup" href="/signup"> - Sign Up - </DropdownItem> - </DropdownMenu> - </Dropdown> - ) - ) : ( - - - user && ( - <NavbarItem> - <Link href="/create-post"> - <Button - endContent={<SquarePen />} - className="text-white border-white/50 hover:border-green-100/50 hover:text-green-100 hover:scale-110 transition-all transform duration-500 ease-in-out" - variant="bordered" - > - Create Post - </Button> - </Link> - <Spacer x={32} /> - </NavbarItem> - ) - <NavbarItem> - <Tooltip - delay={1000} - content={ - <div className="px-1 py-2 text-black text-center"> - <div className="text-small font-bold">GitHub</div> - <div className="text-tiny">Source Code</div> - </div> - } - > - <Link - href="https://github.com/Dare2Jam/" - className="text-white flex justify-center duration-500 ease-in-out transition-all transform hover:scale-125 hover:text-red-100" - isExternal - > - <SiGithub title="" /> - </Link> - </Tooltip> - </NavbarItem> - <NavbarItem> - <Tooltip - delay={1000} - content={ - <div className="px-1 py-2 text-black text-center"> - <div className="text-small font-bold">Forgejo</div> - <div className="text-tiny">Source Code</div> - </div> - } - > - <Link - href="https://git.edikoyo.com/Ategon/Jamjar" - className="text-white flex justify-center duration-500 ease-in-out transition-all transform hover:scale-125 hover:text-red-100" - isExternal - > - <SiForgejo title="" /> - </Link> - </Tooltip> - </NavbarItem> - <NavbarItem> - <Link - href="https://discord.gg/rfmKzM6ASw" - className="text-white flex justify-center duration-500 ease-in-out transition-all transform hover:scale-125 hover:text-indigo-100" - isExternal - > - <SiDiscord /> - </Link> - </NavbarItem> - <Divider orientation="vertical" className="h-1/2" /> - {!user ? ( - <div className="flex gap-3 items-center"> - <NavbarItem> - <Link href="/login"> - <Button - endContent={<LogInIcon />} - className="text-white border-white/50 hover:border-green-100/50 hover:text-green-100 hover:scale-110 transition-all transform duration-500 ease-in-out" - variant="bordered" - > - Log In - </Button> - </Link> - </NavbarItem> - <NavbarItem> - <Link href="/signup"> - <Button - endContent={<NotebookPen />} - className="text-white border-white/50 hover:border-green-100/50 hover:text-green-100 hover:scale-110 transition-all transform duration-500 ease-in-out" - variant="bordered" - > - Sign up - </Button> - </Link> - </NavbarItem> - </div> - ) : ( - <Dropdown> - <DropdownTrigger> - <Avatar src={user.profilePicture} /> - </DropdownTrigger> - <DropdownMenu> - - <DropdownItem - key="logout" - color="danger" - className="text-danger" - href="/logout" - > - Logout - </DropdownItem> - </DropdownMenu> - </Dropdown> - )} - ) - </NavbarContent> */ diff --git a/src/components/posts/PostCard.tsx b/src/components/posts/PostCard.tsx index 2e1b8c3..0624f36 100644 --- a/src/components/posts/PostCard.tsx +++ b/src/components/posts/PostCard.tsx @@ -1,12 +1,13 @@ -import { Avatar, Card, CardBody, Spacer } from "@nextui-org/react"; +import { Avatar, Button, Card, CardBody, Spacer } from "@nextui-org/react"; import { formatDistance } from "date-fns"; import Link from "next/link"; import { PostType } from "@/types/PostType"; +import { Heart, MessageCircle } from "lucide-react"; export default function PostCard({ post }: { post: PostType }) { return ( <Card className="bg-opacity-60"> - <CardBody> + <CardBody className="p-5"> <p className="text-2xl">{post.title}</p> <div className="flex items-center gap-3 text-xs text-default-500 pt-1"> @@ -35,14 +36,14 @@ export default function PostCard({ post }: { post: PostType }) { <Spacer y={4} /> - {/* <div className="flex gap-3"> - <Button size="sm"> + <div className="flex gap-3"> + <Button size="sm" variant="bordered"> <Heart size={16} /> {post.likers.length} </Button> - <Button size="sm"> + <Button size="sm" variant="bordered"> <MessageCircle size={16} /> {0} </Button> - </div> */} + </div> </CardBody> </Card> ); diff --git a/src/components/posts/index.tsx b/src/components/posts/index.tsx index 64f398e..b0ed317 100644 --- a/src/components/posts/index.tsx +++ b/src/components/posts/index.tsx @@ -3,6 +3,7 @@ import { useEffect, useState } from "react"; import PostCard from "./PostCard"; import { PostType } from "@/types/PostType"; +import { Button } from "@nextui-org/react"; export default function Posts() { const [posts, setPosts] = useState<PostType[]>(); @@ -21,13 +22,30 @@ export default function Posts() { }, []); return ( - <div className="flex flex-col gap-3 p-4"> - {posts && - posts.map((post) => ( - <div key={post.id}> - <PostCard post={post} /> - </div> - ))} + <div> + <div className="flex justify-between p-4 pb-0"> + <div className="flex gap-2"> + <Button size="sm" className="text-xs" variant="faded"> + Newest + </Button> + <Button size="sm" className="text-xs" variant="faded"> + All Tags + </Button> + </div> + <div> + <Button size="sm" className="text-xs" variant="faded"> + Cozy + </Button> + </div> + </div> + <div className="flex flex-col gap-3 p-4"> + {posts && + posts.map((post) => ( + <div key={post.id}> + <PostCard post={post} /> + </div> + ))} + </div> </div> ); return <div></div>; diff --git a/src/components/user/index.tsx b/src/components/user/index.tsx deleted file mode 100644 index 790ef8c..0000000 --- a/src/components/user/index.tsx +++ /dev/null @@ -1,30 +0,0 @@ -"use client"; - -// import { useEffect, useState } from "react"; -// import { useParams } from "next/navigation"; - -export default function User() { - // const [user, setUser] = useState(); - // const { slug } = useParams(); - - // useEffect(() => { - // const fetchUser = async () => { - // const response = await fetch(`http://localhost:3005/api/v1/user?slug=${slug}`) - // setUser(await response.json()); - // } - - // fetchUser(); - // }, []) - - // return ( - // <div> - // {user && ( - // <div> - // <p>{user.name}</p> - // <p>{user.bio}</p> - // </div> - // )} - // </div> - // ); - return <div></div>; -}