Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add routing #653

Merged
merged 5 commits into from
Jun 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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