-
Notifications
You must be signed in to change notification settings - Fork 3
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
Add routing #653
Changes from 4 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 [episode, setEpisode] = 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={{ episode, setEpisode }}> | ||
<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: /:episode/resources, /:episode/tournaments, /:episode/rankings, /:episode/queue | ||
{ path: "/:episode/home", element: <Home /> }, | ||
{ path: "/:episode/quickstart", element: <QuickStart /> }, | ||
{ path: "/:episode/*", element: <NotFound /> }, | ||
// Pages that should only be visible when logged in | ||
// TODO: /:episode/team, /:episode/submissions, /:episode/scrimmaging | ||
{ path: "/account", element: <Account /> }, | ||
// etc | ||
], | ||
}, | ||
// Pages that should redirect | ||
{ path: "/*", element: <Navigate to={`/${DEFAULT_EPISODE}/home`} /> }, | ||
]); | ||
|
||
export default App; |
This file was deleted.
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 { episode } = useParams(); | ||
if (episode !== undefined && episode !== episodeContext.episode) { | ||
episodeContext.setEpisode(episode); | ||
} | ||
return ( | ||
<div className="h-screen"> | ||
<Navbar/> | ||
<div className="flex flex-row h-full"> | ||
<Sidebar /> | ||
<Outlet /> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default EpisodeLayout; |
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; |
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; |
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; |
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('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('should collapse sidebar', () => { | ||
render(<MemoryRouter><Sidebar collapsed={true} /></MemoryRouter>); | ||
expect(screen.queryByText('Home')).toBeNull(); | ||
}); | ||
|
||
test('should link to episode in surrounding context', () => { | ||
render(<MemoryRouter> | ||
<EpisodeContext.Provider value={{ episode: "something", setEpisode: (_) => undefined }}> | ||
<Sidebar /> | ||
</EpisodeContext.Provider></MemoryRouter> | ||
); | ||
const linkElement = screen.getByText('Resources').closest('a')?.getAttribute('href'); | ||
expect(linkElement).toEqual(expect.stringContaining(`/something/resources`)); | ||
}); |
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 { episode } = useContext(EpisodeContext); | ||
const linkBase = `/${episode}/`; | ||
|
||
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; |
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. | ||
episode: DEFAULT_EPISODE, | ||
setEpisode: (episode: string) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Q: should we standardize how we refer to the current episode? I have been calling it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should def standardize this What if we just call name_short There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree with this ^^ |
||
console.log("default episode"); | ||
}, | ||
}); |
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, | ||
}, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export const DEFAULT_EPISODE = "bc23"; |
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 { episode } = useContext(EpisodeContext); | ||
return <p>the episode is {episode} and this is the account page</p>; | ||
}; | ||
|
||
export default Account; |
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; |
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; |
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; |
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; |
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; |
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; |
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 QuickStart: React.FC = () => { | ||
const { episode, setEpisode } = useContext(EpisodeContext); | ||
return <p>quickstart page</p>; | ||
}; | ||
|
||
export default QuickStart; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My tests are currently not working but when they are we will have dozens... thinking this may be a good opportunity to use the
API/UI: <test message> (STABLE/UNSTABLE)
format for test messages?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
update after discussion: we won't include (stable/unstable), but we will include api/ui