Skip to content

Commit

Permalink
Merge pull request #653 from battlecode/serena/routing
Browse files Browse the repository at this point in the history
Add routing
  • Loading branch information
acrantel authored Jun 25, 2023
2 parents d0d21f4 + 502c246 commit a571eae
Show file tree
Hide file tree
Showing 22 changed files with 330 additions and 27 deletions.
4 changes: 2 additions & 2 deletions frontend2/public/index.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" class="h-full bg-gray-100">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
Expand All @@ -26,7 +26,7 @@
-->
<title>React App</title>
</head>
<body>
<body class="h-full">
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
Expand Down
64 changes: 48 additions & 16 deletions frontend2/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,55 @@
import React from "react";
import React, { useState } from "react";
import EpisodeLayout from "./components/EpisodeLayout";
import Home from "./views/Home";
import Logout from "./views/Logout";
import Register from "./views/Register";
import PasswordForgot from "./views/PasswordForgot";
import PasswordChange from "./views/PasswordChange";
import Account from "./views/Account";
import Login from "./views/Login";
import QuickStart from "./views/QuickStart";
import { EpisodeContext } from "./contexts/EpisodeContext";
import {
RouterProvider,
createBrowserRouter,
Navigate,
} from "react-router-dom";
import { DEFAULT_EPISODE } from "./utils/constants";
import NotFound from "./views/NotFound";

const App: React.FC = () => {
const [episodeId, setEpisodeId] = useState(DEFAULT_EPISODE);
return (
<div className="App">
<header className="App-header text-3xl font-bold underline">
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
<EpisodeContext.Provider value={{ episodeId, setEpisodeId }}>
<RouterProvider router={router} />
</EpisodeContext.Provider>
);
};

const router = createBrowserRouter([
// Pages that should render without a sidebar/navbar
{ path: "/login", element: <Login /> },
{ path: "/logout", element: <Logout /> },
{ 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/rankings, /:episodeId/queue
{ path: "/:episodeId/home", element: <Home /> },
{ path: "/:episodeId/quickstart", element: <QuickStart /> },
{ path: "/:episodeId/*", element: <NotFound /> },
// Pages that should only be visible when logged in
// TODO: /:episodeId/team, /:episodeId/submissions, /:episodeId/scrimmaging
{ path: "/account", element: <Account /> },
// etc
],
},
// Pages that should redirect
{ path: "/*", element: <Navigate to={`/${DEFAULT_EPISODE}/home`} /> },
]);

export default App;
9 changes: 0 additions & 9 deletions frontend2/src/__test__/App.test.tsx

This file was deleted.

26 changes: 26 additions & 0 deletions frontend2/src/components/EpisodeLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React, { useContext } from "react";
import Navbar from "./Navbar";
import Sidebar from "./sidebar";
import { Outlet, useParams } from "react-router-dom";
import { EpisodeContext } from "../contexts/EpisodeContext";

// This component contains the NavBar and SideBar.
// Child route components are rendered with <Outlet />
const EpisodeLayout: React.FC = () => {
const episodeContext = useContext(EpisodeContext);
const { episodeId } = useParams();
if (episodeId !== undefined && episodeId !== episodeContext.episodeId) {
episodeContext.setEpisodeId(episodeId);
}
return (
<div className="h-screen">
<Navbar/>
<div className="flex flex-row h-full">
<Sidebar />
<Outlet />
</div>
</div>
);
};

export default EpisodeLayout;
7 changes: 7 additions & 0 deletions frontend2/src/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from "react";

const Navbar: React.FC = () => {
return <p>navbar here</p>;
};

export default Navbar;
34 changes: 34 additions & 0 deletions frontend2/src/components/sidebar/SidebarItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from "react";
import { NavLink } from "react-router-dom";

interface SidebarItemProps {
icon: React.ReactNode;
text: string;
linkTo: string;
}

const SidebarItem: React.FC<SidebarItemProps> = ({
icon,
text,
linkTo,
}) => {
const baseStyle =
"text-base flex items-center gap-3 ";
const colorVariants = {
gray: "text-gray-800 hover:text-gray-400",
color: "text-teal",
};
return (
<NavLink
className={({ isActive }) =>
baseStyle + (isActive ? colorVariants.color : colorVariants.gray)
}
to={linkTo}
>
{icon}
{text}
</NavLink>
);
};

export default SidebarItem;
19 changes: 19 additions & 0 deletions frontend2/src/components/sidebar/SidebarSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from "react";

interface SidebarSectionProps {
children?: React.ReactNode,
title?: string;
}

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

export default SidebarSection;
27 changes: 27 additions & 0 deletions frontend2/src/components/sidebar/__test__/Sidebar.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import Sidebar from "../";
import { DEFAULT_EPISODE } from "../../../utils/constants";
import { EpisodeContext } from "../../../contexts/EpisodeContext";
import { MemoryRouter } from "react-router-dom";

test('UI: should link to default episode', () => {
render(<MemoryRouter><Sidebar /></MemoryRouter>);
const linkElement = screen.getByText('Resources').closest('a')?.getAttribute('href');
expect(linkElement).toEqual(expect.stringContaining(`/${DEFAULT_EPISODE}/resources`));
});

test('UI: should collapse sidebar', () => {
render(<MemoryRouter><Sidebar collapsed={true} /></MemoryRouter>);
expect(screen.queryByText('Home')).toBeNull();
});

test('UI: should link to episode in surrounding context', () => {
render(<MemoryRouter>
<EpisodeContext.Provider value={{ episodeId: "something", setEpisodeId: (_) => undefined }}>
<Sidebar />
</EpisodeContext.Provider></MemoryRouter>
);
const linkElement = screen.getByText('Resources').closest('a')?.getAttribute('href');
expect(linkElement).toEqual(expect.stringContaining(`/something/resources`));
});
83 changes: 83 additions & 0 deletions frontend2/src/components/sidebar/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React, { useContext } from "react";
import SidebarSection from "./SidebarSection";
import SidebarItem from "./SidebarItem";
import {
ClipboardDocumentIcon, HomeIcon, MapIcon,
TrophyIcon, ChartBarIcon, ClockIcon,
UserGroupIcon, ArrowUpTrayIcon, PlayCircleIcon,
} from "@heroicons/react/24/outline";
import { EpisodeContext } from "../../contexts/EpisodeContext";

interface SidebarProps {
collapsed?: boolean;
}

const Sidebar: React.FC<SidebarProps> = ({
collapsed
}) => {
collapsed = collapsed ?? false;
const { episodeId } = useContext(EpisodeContext);
const linkBase = `/${episodeId}/`;

return (
collapsed ? null : (
<div className="flex flex-col gap-8 py-4 h-full bg-gray-50 shadow-gray-200 shadow-sm">
<SidebarSection title="" >
<SidebarItem
icon={<HomeIcon className="h-6 w-6" />}
text="Home"
linkTo={`${linkBase}home`}
/>
<SidebarItem
icon={<MapIcon className="h-6 w-6" />}
text="Quick Start"
linkTo={`${linkBase}quickstart`}
/>
<SidebarItem
icon={<ClipboardDocumentIcon className="h-6 w-6" />}
text="Resources"
linkTo={`${linkBase}resources`}
/>
</SidebarSection>
<SidebarSection title="compete" >
<SidebarItem
icon={<TrophyIcon className="h-6 w-6" />}
text="Tournaments"
linkTo={`${linkBase}tournaments`}
/>
<SidebarItem
icon={<ChartBarIcon className="h-6 w-6" />}
text="Rankings"
linkTo={`${linkBase}rankings`}
/>
<SidebarItem
icon={<ClockIcon className="h-6 w-6" />}
text="Queue"
linkTo={`${linkBase}queue`}
/>
</SidebarSection>
<SidebarSection title="team management" >
<SidebarItem
icon={<UserGroupIcon className="h-6 w-6" />}
text="My Team"
linkTo={`${linkBase}team`}
/>
<SidebarItem
icon={<ArrowUpTrayIcon className="h-6 w-6" />}
text="Submissions"
linkTo={`${linkBase}submission`}
/>
<SidebarItem
icon={<PlayCircleIcon className="h-6 w-6" />}
text="Scrimmaging"
linkTo={`${linkBase}scrimmaging`}
/>
</SidebarSection>
</div>
)
);


};

export default Sidebar;
10 changes: 10 additions & 0 deletions frontend2/src/contexts/EpisodeContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { createContext } from "react";
import { DEFAULT_EPISODE } from "../utils/constants";

export const EpisodeContext = createContext({
// the default episode.
episodeId: DEFAULT_EPISODE,
setEpisodeId: (episodeId: string) => {
console.log("default episode");
},
});
7 changes: 7 additions & 0 deletions frontend2/src/episodeData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Home from "./views/Home";

export const AppModules = {
bc23: {
home: Home,
},
};
1 change: 1 addition & 0 deletions frontend2/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import "./index.css";
import App from "./App";

Expand Down
1 change: 1 addition & 0 deletions frontend2/src/utils/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const DEFAULT_EPISODE = "bc23";
9 changes: 9 additions & 0 deletions frontend2/src/views/Account.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React, { useContext } from "react";
import { EpisodeContext } from "../contexts/EpisodeContext";

const Account: React.FC = () => {
const { episodeId } = useContext(EpisodeContext);
return <p>the episode is {episodeId} and this is the account page</p>;
};

export default Account;
7 changes: 7 additions & 0 deletions frontend2/src/views/Home.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from "react";

const Home: React.FC = () => {
return <p>Homepage</p>;
};

export default Home;
7 changes: 7 additions & 0 deletions frontend2/src/views/Login.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from "react";

const Login: React.FC = () => {
return <p>login page</p>;
};

export default Login;
7 changes: 7 additions & 0 deletions frontend2/src/views/Logout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from "react";

const Logout: React.FC = () => {
return <p>logout page</p>;
};

export default Logout;
7 changes: 7 additions & 0 deletions frontend2/src/views/NotFound.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from "react";

const NotFound: React.FC = () => {
return <p>That page was not found.</p>;
};

export default NotFound;
7 changes: 7 additions & 0 deletions frontend2/src/views/PasswordChange.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from "react";

const PasswordChange: React.FC = () => {
return <p>passwordchange page</p>;
};

export default PasswordChange;
7 changes: 7 additions & 0 deletions frontend2/src/views/PasswordForgot.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from "react";

const PasswordForgot: React.FC = () => {
return <p>passwordforgot page</p>;
};

export default PasswordForgot;
7 changes: 7 additions & 0 deletions frontend2/src/views/QuickStart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from "react";

const QuickStart: React.FC = () => {
return <p>quickstart page</p>;
};

export default QuickStart;
Loading

0 comments on commit a571eae

Please sign in to comment.