Skip to content

Commit

Permalink
Header and minor ui changes (#654)
Browse files Browse the repository at this point in the history
Co-authored-by: Serena Li <serena.li.usa@gmail.com>
  • Loading branch information
apollo1291 and acrantel committed Feb 8, 2024
1 parent f5fb636 commit 93aedd0
Show file tree
Hide file tree
Showing 18 changed files with 288 additions and 96 deletions.
Binary file added frontend2/public/battlecode-logo-horiz-white.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend2/public/battlecode-logo-vert-white.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed frontend2/public/favicon.ico
Binary file not shown.
File renamed without changes
Binary file removed frontend2/public/logo192.png
Binary file not shown.
Binary file removed frontend2/public/logo512.png
Binary file not shown.
34 changes: 21 additions & 13 deletions frontend2/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
RouterProvider,
createBrowserRouter,
Navigate,
redirect,
} from "react-router-dom";
import { DEFAULT_EPISODE } from "./utils/constants";
import NotFound from "./views/NotFound";
Expand All @@ -38,31 +39,38 @@ const router = createBrowserRouter([
{ path: "/register", element: <Register /> },
{ path: "/password_forgot", element: <PasswordForgot /> },
{ path: "/password_change", element: <PasswordChange /> },
// Pages that will contain the episode specific sidebar and navbar
{
element: <EpisodeLayout />,
children: [
// Pages that should always be visible
// TODO: /:episodeId/resources, /:episodeId/tournaments, /:episodeId/queue
{ path: "/:episodeId/home", element: <Home /> },
{ path: "/:episodeId/quickstart", element: <QuickStart /> },
{ path: "/:episodeId/*", element: <NotFound /> },
{ path: "/:episodeId/rankings", element: <Rankings /> },
],
},
// Pages that should only be visible when logged in
{
element: <PrivateRoute />,
children: [
{ path: "/account", element: <Account /> },
{
element: <EpisodeLayout />,
children: [
// TODO: /:episodeId/team, /:episodeId/submissions, /:episodeId/scrimmaging
],
},
{ path: "/account", element: <Account /> },
],
},
// Pages that will contain the episode specific sidebar and navbar
{
element: <EpisodeLayout />,
children: [
// Pages that should always be visible
// TODO: /:episodeId/resources, /:episodeId/tournaments, /:episodeId/queue
{ path: "/:episodeId/home", element: <Home /> },
{
path: "/:episodeId/",
loader: ({ params }) => {
return redirect(`/${params.episodeId as string}/home`);
},
},
{ path: "/:episodeId/quickstart", element: <QuickStart /> },
{ path: "/:episodeId/*", element: <NotFound /> },
{ path: "/:episodeId/rankings", element: <Rankings /> },
],
},

// Pages that should redirect
{ path: "/*", element: <Navigate to={`/${DEFAULT_EPISODE}/home`} /> },
]);
Expand Down
3 changes: 3 additions & 0 deletions frontend2/src/components/CurrentUserProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import { removeApiTokens } from "../utils/cookies";
import { loginCheck } from "../utils/api/auth";
import { getUserUserProfile } from "../utils/api/user";
import Cookies from "js-cookie";

export const CurrentUserProvider: React.FC<{ children: React.ReactNode }> = ({
children,
Expand All @@ -26,6 +27,8 @@ export const CurrentUserProvider: React.FC<{ children: React.ReactNode }> = ({
});
};
const logout = (): void => {
Cookies.remove("access");
Cookies.remove("refresh");
setUserData({
authState: AuthStateEnum.NOT_AUTHENTICATED,
});
Expand Down
6 changes: 3 additions & 3 deletions frontend2/src/components/EpisodeLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { useContext } from "react";
import Navbar from "./Navbar";
import Header from "./Header";
import Sidebar from "./sidebar";
import { Outlet, useParams } from "react-router-dom";
import { EpisodeContext } from "../contexts/EpisodeContext";

// This component contains the NavBar and SideBar.
// This component contains the Header and SideBar.
// Child route components are rendered with <Outlet />
const EpisodeLayout: React.FC = () => {
const episodeContext = useContext(EpisodeContext);
Expand All @@ -14,7 +14,7 @@ const EpisodeLayout: React.FC = () => {
}
return (
<div className="h-screen">
<Navbar />
<Header />
<div className="flex h-full flex-row">
<Sidebar />
<Outlet />
Expand Down
148 changes: 148 additions & 0 deletions frontend2/src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import React, { Fragment, useContext } from "react";
import { Menu, Transition } from "@headlessui/react";
import { Link, NavLink } from "react-router-dom";
import { AuthStateEnum, useCurrentUser } from "../contexts/CurrentUserContext";
import Icon from "./elements/Icon";
import { EpisodeContext } from "../contexts/EpisodeContext";
import { SIDEBAR_ITEM_DATA } from "./sidebar";

const Header: React.FC = () => {
const { authState, logout, user } = useCurrentUser();
const { episodeId } = useContext(EpisodeContext);

return (
<nav className="bg-gray-700">
<div className="w-full px-2 sm:px-6 lg:px-8">
<div className="relative flex h-16 items-center justify-between">
{/* mobile menu */}
<Menu>
<div className="absolute inset-y-0 left-3 flex items-center sm:hidden">
<Menu.Button className="rounded-md px-1 py-1.5 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white">
<span className="sr-only">Open main menu</span>
<Icon
name="bars_3"
className="text-gray-300 hover:text-white"
size="lg"
/>
</Menu.Button>
</div>
<Transition
as={Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Menu.Items className="absolute left-1 top-12 z-10 mt-2 w-64 gap-2 rounded-md bg-white p-2 font-light shadow-lg sm:hidden">
{SIDEBAR_ITEM_DATA.map(({ iconName, text, linkTo }, index) => (
<Menu.Item key={index}>
<NavLink
className={({ isActive }) =>
"flex items-center gap-3 rounded-lg py-2 pl-1.5 pr-8 text-base " +
(isActive
? "cursor-default text-cyan-600"
: "text-gray-800 ui-active:bg-gray-500 ui-active:text-gray-100")
}
to={`/${episodeId}/${linkTo}`}
>
<Icon name={iconName} size="md" />
{text}
</NavLink>
</Menu.Item>
))}

{authState === AuthStateEnum.NOT_AUTHENTICATED && (
<>
<hr className="m-2" />
<Menu.Item as="div" className="w-full pb-2 pt-1">
<Link
to="/login"
className="rounded-full px-2.5 text-cyan-600 ring-0 hover:bg-gray-100/10"
>
Log in
</Link>
</Menu.Item>
</>
)}
</Menu.Items>
</Transition>
</Menu>
{/* battlecode logo */}
<div className="flex flex-1 items-center justify-center sm:items-stretch sm:justify-start">
<div className="flex flex-shrink-0 items-center">
<img
className="hidden h-8 sm:block"
src="/battlecode-logo-horiz-white.png"
alt="Battlecode Logo"
/>
<img
className="h-10 sm:hidden"
src="/battlecode-logo-vert-white.png"
alt="Battlecode Logo"
/>
</div>
<div className="hidden sm:ml-6 sm:block"></div>
</div>
{/* profile menu (if the user is logged in) */}
{authState === AuthStateEnum.AUTHENTICATED && (
<Menu>
<div className="absolute inset-y-0 right-0 flex items-center pr-2 sm:pr-0">
<Menu.Button className="rounded-full focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800">
<span className="sr-only">Open user menu</span>
<img
className="h-8 w-8 rounded-full bg-white"
src={user?.profile?.avatar_url}
alt="Profile Picture"
/>
</Menu.Button>
</div>
<Transition
as={Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Menu.Items className="absolute right-1 top-12 z-10 mt-2 w-40 rounded-md bg-white py-1 text-gray-800 shadow-lg">
<Menu.Item>
<Link
className="flex w-full items-center rounded-lg px-4 py-2 sm:text-sm "
to="/account"
>
Your profile
</Link>
</Menu.Item>
<Menu.Item>
<button
onClick={logout}
className="flex w-full items-center rounded-lg px-4 py-2 sm:text-sm"
>
Sign out
</button>
</Menu.Item>
</Menu.Items>
</Transition>
</Menu>
)}
{/* sign up / login button */}
{authState === AuthStateEnum.NOT_AUTHENTICATED && (
<div className="absolute inset-y-0 right-1 flex flex-row items-center justify-center gap-3">
<Link
to="/login"
className="hidden rounded-full px-3 py-1.5 text-gray-200 ring-0 hover:bg-gray-100/10 sm:block"
>
Log in
</Link>
<Link
to="/register"
className="rounded-full px-3 py-1.5 text-center text-white ring-2 ring-inset ring-gray-200 hover:bg-gray-100/10"
>
Register
</Link>
</div>
)}
</div>
</div>
</nav>
);
};

export default Header;
7 changes: 0 additions & 7 deletions frontend2/src/components/Navbar.tsx

This file was deleted.

17 changes: 10 additions & 7 deletions frontend2/src/components/elements/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import React from "react";
import Icon, { type IconName } from "./Icon";

const VARIANTS: Record<string, string> = {
"": "bg-gray-50 text-gray-800 hover:bg-gray-100 hover:ring-gray-900 hover:text-black ring-gray-500 ring-1 ring-inset",
dark: "bg-cyan-700 text-white hover:bg-cyan-800",
"light-outline":
"ring-2 ring-inset ring-gray-200 text-gray-200 hover:bg-gray-100/20",
} as const;

interface ButtonProps extends React.ComponentPropsWithoutRef<"button"> {
variant?: string;
label?: string;
Expand All @@ -9,11 +16,6 @@ interface ButtonProps extends React.ComponentPropsWithoutRef<"button"> {
className?: string;
}

const variants: Record<string, string> = {
"": "bg-gray-50 text-gray-800 hover:bg-gray-100 hover:ring-gray-900 hover:text-black ring-gray-500 ring-1 ring-inset",
dark: "bg-cyan-700 text-white hover:bg-cyan-800",
};

const Button: React.FC<ButtonProps> = ({
variant = "",
label,
Expand All @@ -22,14 +24,15 @@ const Button: React.FC<ButtonProps> = ({
className = "",
...rest
}) => {
const variantStyle = `${variants[variant]} ${
const variantStyle = `${VARIANTS[variant]} ${
fullWidth ? "w-full" : ""
} ${className}`;
return (
<button
// default button type
type="button"
className={`flex h-9 flex-row items-center justify-center gap-1.5 rounded px-2.5 py-1.5 font-medium shadow-sm ${variantStyle}`}
className={`flex h-9 flex-row items-center justify-center gap-1.5 rounded px-2.5
py-1.5 shadow-sm ${variantStyle} ${className}`}
{...rest}
>
{iconName !== undefined && <Icon name={iconName} size="sm" />}
Expand Down
12 changes: 9 additions & 3 deletions frontend2/src/components/elements/Icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
ChevronDownIcon as ChevronDownIcon24,
CheckIcon as CheckIcon24,
InformationCircleIcon as InformationCircleIcon24,
Bars3Icon as Bars3Icon24,
} from "@heroicons/react/24/outline";

import {
Expand All @@ -27,6 +28,7 @@ import {
ChevronDownIcon as ChevronDownIcon20,
CheckIcon as CheckIcon20,
InformationCircleIcon as InformationCircleIcon20,
Bars3Icon as Bars3Icon20,
} from "@heroicons/react/20/solid";

const icons24 = {
Expand All @@ -42,6 +44,7 @@ const icons24 = {
chevron_down: ChevronDownIcon24,
check: CheckIcon24,
information_circle: InformationCircleIcon24,
bars_3: Bars3Icon24,
};

const icons20 = {
Expand All @@ -57,28 +60,31 @@ const icons20 = {
chevron_down: ChevronDownIcon20,
check: CheckIcon20,
information_circle: InformationCircleIcon20,
bars_3: Bars3Icon20,
};

export type IconName = keyof typeof icons24 | keyof typeof icons20;

export interface IconProps {
name: IconName;
size?: "sm" | "md" | "xs";
size?: "sm" | "md" | "xs" | "lg";
className?: string;
}

const sizeToClass = {
xs: "h-4 w-4",
sm: "h-5 w-5",
md: "h-6 w-6",
xs: "h-4 w-4",
lg: "h-7 w-7",
};

const Icon: React.FC<IconProps> = ({
name,
size = "md",
className = "",
}: IconProps) => {
const IconComponent = size === "md" ? icons24[name] : icons20[name];
const IconComponent =
size === "md" || size === "lg" ? icons24[name] : icons20[name];
return <IconComponent className={`${sizeToClass[size]} ${className}`} />;
};

Expand Down
8 changes: 4 additions & 4 deletions frontend2/src/components/sidebar/SidebarItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ const SidebarItem: React.FC<SidebarItemProps> = ({
text,
linkTo,
}) => {
const baseStyle = "text-base flex items-center gap-3 ";
const colorVariants = {
gray: "text-gray-800 hover:text-gray-400",
color: "text-teal",
gray: "text-gray-800 hover:text-gray-100 hover:bg-gray-500",
color: "text-cyan-600",
};
return (
<NavLink
className={({ isActive }) =>
baseStyle + (isActive ? colorVariants.color : colorVariants.gray)
"flex items-center gap-3 rounded-lg py-2 pl-1.5 pr-8 text-base " +
(isActive ? colorVariants.color : colorVariants.gray)
}
to={linkTo}
>
Expand Down
6 changes: 3 additions & 3 deletions frontend2/src/components/sidebar/SidebarSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ interface SidebarSectionProps {

const SidebarSection: React.FC<SidebarSectionProps> = ({ children, title }) => {
return (
<div className="pl-5 pr-8">
<div className="px-4">
{title !== undefined && (
<h2 className="mb-3 text-sm uppercase tracking-wider text-gray-500">
<h2 className="mx-auto mb-2 font-light uppercase text-gray-500">
{title}
</h2>
)}
<div className="flex flex-col gap-5">{children}</div>
<div className="flex flex-col gap-1">{children}</div>
</div>
);
};
Expand Down
Loading

0 comments on commit 93aedd0

Please sign in to comment.