Skip to content

Commit

Permalink
Add BaseCard and Header
Browse files Browse the repository at this point in the history
consistency

responsive header

update header

remove chatgpt comments

finish up header and fix up other components

lint and delete basecard (will be merged in future pr)

Add BaseCard and Header

Base Card/text components and veiws

make header/sidebar fixed, create documentationpage for quickstart/resources
  • Loading branch information
apollo1291 authored and acrantel committed Aug 30, 2023
1 parent d1bf2de commit b5c1984
Show file tree
Hide file tree
Showing 28 changed files with 1,417 additions and 116 deletions.
902 changes: 895 additions & 7 deletions frontend2/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions frontend2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"react-dom": "^18.2.0",
"react-hook-form": "^7.45.1",
"react-router-dom": "^6.13.0",
"react-markdown": "^8.0.7",
"web-vitals": "^2.1.4"
},
"scripts": {
Expand Down
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.
37 changes: 24 additions & 13 deletions frontend2/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ import {
RouterProvider,
createBrowserRouter,
Navigate,
redirect,
} from "react-router-dom";
import { DEFAULT_EPISODE } from "./utils/constants";
import NotFound from "./views/NotFound";
import Rankings from "./views/Rankings";
import { CurrentUserProvider } from "./components/CurrentUserProvider";
import PrivateRoute from "./components/PrivateRoute";
import Resources from "./views/Resources";

const App: React.FC = () => {
const [episodeId, setEpisodeId] = useState(DEFAULT_EPISODE);
Expand All @@ -38,31 +40,40 @@ 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/tournaments, /:episodeId/queue
{ path: "/:episodeId/resources", element: <Resources /> },
{ path: "/:episodeId/quickstart", element: <QuickStart /> },
{ 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
28 changes: 28 additions & 0 deletions frontend2/src/components/BaseCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';

interface BaseCardProps {
GameImage: string;
GameName: string;
GameDescr: string;
GameYear: number;
}

const YearCard: React.FC<BaseCardProps> = ({ GameImage, GameName, GameDescr, GameYear }) => {
const redirect = (): void => {
console.log(GameYear.toString()); // Redirect to year page
};

return (
<div className="max-w-sm rounded overflow-hidden shadow-md transition duration-300 ease-in-out transform hover:shadow-lg hover:cursor-pointer" onClick={redirect}>
<div className="aspect-w-4 aspect-h-3">
<img className="object-cover" src={require(`../${GameImage}`)} alt={GameName} />
</div>
<div className="px-6 py-4">
<div className="font-bold text-xl mb-2">{GameName}</div>
<p className="text-gray-700 text-base">{GameDescr}</p>
</div>
</div>
);
};

export default YearCard;
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
48 changes: 48 additions & 0 deletions frontend2/src/components/DocumentationPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from "react";
import ReactMarkdown from "react-markdown";
import { Link } from "react-router-dom";

const isInternalLink = (to: string): boolean => {
const url = new URL(to, window.location.origin);
console.log(url, window.location.hostname);
return url.hostname === window.location.hostname;
};

interface DocumentationPageProps {
text: string;
}

const DocumentationPage: React.FC<DocumentationPageProps> = ({ text }) => {
return (
<div className="h-full w-full overflow-auto bg-white p-6">
<ReactMarkdown
components={{
a: ({ href, ...props }) => {
const target = href ?? "";
if (isInternalLink(target)) {
return (
<Link
className="text-cyan-600 hover:underline"
to={target}
{...props}
/>
);
} else {
return (
<a
className="text-cyan-600 hover:underline"
href={target}
{...props}
/>
);
}
},
}}
>
{text}
</ReactMarkdown>
</div>
);
};

export default DocumentationPage;
12 changes: 6 additions & 6 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 @@ -13,10 +13,10 @@ const EpisodeLayout: React.FC = () => {
episodeContext.setEpisodeId(episodeId);
}
return (
<div className="h-screen">
<Navbar />
<div className="flex h-full flex-row">
<Sidebar />
<div className="h-screen overflow-auto">
<Header />
<Sidebar />
<div className="fixed right-0 h-full pt-16 sm:left-52">
<Outlet />
</div>
</div>
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="fixed top-0 h-16 w-full 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.

Loading

0 comments on commit b5c1984

Please sign in to comment.