Skip to content
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
81 changes: 46 additions & 35 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,25 @@ import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import AppLayout from "./components/app-layout/AppLayout";
import { CssBaseline, ThemeProvider } from "@mui/material";
import { createAppTheme } from "./components/common/styles/theme";
import Leaderboard from "./pages/leaderboard/Leaderboard";
import LeaderboardEditor from "./pages/leaderboard/LeaderboardEditor";
import Home from "./pages/home/Home";
import News from "./pages/news/News";
import WorkingGroups from "./pages/working-groups/WorkingGroups";
import Lectures from "./pages/lectures/Lectures";
import Live from "./pages/live/Live";
import ErrorPage from "./pages/Error";
import Login from "./pages/login/login";
import Privacy from "./pages/privacy/Privacy";
import Loading from "./components/common/loading";
import { useAuthStore } from "./lib/store/authStore";
import { useThemeStore } from "./lib/store/themeStore";
import { useEffect, useMemo } from "react";
import { lazy, Suspense, useEffect, useMemo } from "react";

const Leaderboard = lazy(() => import("./pages/leaderboard/Leaderboard"));
const LeaderboardEditor = lazy(
() => import("./pages/leaderboard/LeaderboardEditor"),
);
const News = lazy(() => import("./pages/news/News"));
const WorkingGroups = lazy(
() => import("./pages/working-groups/WorkingGroups"),
);
const Lectures = lazy(() => import("./pages/lectures/Lectures"));
const Live = lazy(() => import("./pages/live/Live"));
const Login = lazy(() => import("./pages/login/login"));
const Privacy = lazy(() => import("./pages/privacy/Privacy"));

const errorRoutes = [
{
Expand Down Expand Up @@ -67,34 +73,39 @@ function App() {
<CssBaseline />
<BrowserRouter basename="">
<AppLayout>
<Routes>
<Route path="/" element={<Navigate to="/home" replace />} />
<Route path="/home" element={<Home />} />
<Route path="/leaderboard/:id" element={<Leaderboard />} />
<Route path="/leaderboard/:id/editor" element={<LeaderboardEditor />} />
<Route path="/news" element={<News />} />
<Route path="/news/:slug" element={<News />} />
<Route path="/working-groups" element={<WorkingGroups />} />
<Route path="/events" element={<Lectures />} />
<Route path="/lectures" element={<Lectures />} />
<Route path="/live" element={<Live />} />
<Route path="/login" element={<Login />} />
<Route path="/privacy" element={<Privacy />} />
{/* error handling page */}
{errorRoutes.map(({ path, code, title, description }) => (
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/" element={<Navigate to="/home" replace />} />
<Route path="/home" element={<Home />} />
<Route path="/leaderboard/:id" element={<Leaderboard />} />
<Route
key={path}
path={path}
element={
<ErrorPage
code={code}
title={title}
description={description}
/>
}
path="/leaderboard/:id/editor"
element={<LeaderboardEditor />}
/>
))}
</Routes>
<Route path="/news" element={<News />} />
<Route path="/news/:slug" element={<News />} />
<Route path="/working-groups" element={<WorkingGroups />} />
<Route path="/events" element={<Lectures />} />
<Route path="/lectures" element={<Lectures />} />
<Route path="/live" element={<Live />} />
<Route path="/login" element={<Login />} />
<Route path="/privacy" element={<Privacy />} />
{/* error handling page */}
{errorRoutes.map(({ path, code, title, description }) => (
<Route
key={path}
path={path}
element={
<ErrorPage
code={code}
title={title}
description={description}
/>
}
/>
))}
</Routes>
</Suspense>
</AppLayout>
</BrowserRouter>
</ThemeProvider>
Expand Down
1 change: 1 addition & 0 deletions frontend/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export interface LeaderboardDetail {
rankings: Record<
string,
Array<{
user_id: string;
file_name: string;
prev_score: number;
rank: number;
Expand Down
56 changes: 28 additions & 28 deletions frontend/src/components/app-layout/NavBar.test.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,37 @@
import { render, screen } from "@testing-library/react";
import { fireEvent, screen } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import { useLocation } from "react-router-dom";
import { renderWithRouter } from "../../tests/test-utils";
import NavBar from "./NavBar";

describe("NavBar", () => {
it("renders Working Groups link with correct URL", () => {
render(<NavBar />);

const workingGroupsLink = screen.getByRole("link", {
name: /working groups/i,
});
function CurrentPath() {
return <output data-testid="current-path">{useLocation().pathname}</output>;
}

expect(workingGroupsLink).toBeInTheDocument();
expect(workingGroupsLink).toHaveAttribute("href", "/working-groups");
// Internal link should not have target="_blank" or rel="noopener"
expect(workingGroupsLink).not.toHaveAttribute("target", "_blank");
expect(workingGroupsLink).not.toHaveAttribute("rel", "noopener");
describe("NavBar", () => {
it("renders the site navigation links", () => {
renderWithRouter(<NavBar />);
for (const [name, href] of [
["News", "/news"],
["Events", "/events"],
["Projects", "/working-groups"],
]) {
const link = screen.getByRole("link", { name });
expect(link).toHaveAttribute("href", href);
expect(link).not.toHaveAttribute("target", "_blank");
}
});

it("renders all expected navigation links in correct order", () => {
render(<NavBar />);

const links = screen.getAllByRole("link");
const navigationLinks = links.filter((link) =>
["News", "Working Groups", "Events", "Resources", "Docs"].includes(
link.textContent || "",
),
it("navigates inside the app without reloading the document", () => {
renderWithRouter(
<>
<NavBar />
<CurrentPath />
</>,
);

expect(navigationLinks).toHaveLength(5);
expect(navigationLinks[0]).toHaveTextContent("News");
expect(navigationLinks[1]).toHaveTextContent("Working Groups");
expect(navigationLinks[2]).toHaveTextContent("Events");
expect(navigationLinks[3]).toHaveTextContent("Resources");
expect(navigationLinks[4]).toHaveTextContent("Docs");
fireEvent.click(screen.getByRole("link", { name: "News" }));
expect(screen.getByTestId("current-path")).toHaveTextContent("/news");
fireEvent.click(screen.getByRole("link", { name: "GPU MODE" }));
expect(screen.getByTestId("current-path")).toHaveTextContent("/");
});
});
6 changes: 4 additions & 2 deletions frontend/src/components/app-layout/NavBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { appBarStyle, brandStyle } from "./styles";
import { ConstrainedContainer } from "./ConstrainedContainer";
import NavUserProfile from "./NavUserProfile";
import { useThemeStore } from "../../lib/store/themeStore";
import { Link as RouterLink } from "react-router-dom";

export interface NavLink {
label: string;
Expand Down Expand Up @@ -61,7 +62,7 @@ export default function NavBar() {

const Brand = () => (
<Box sx={brandStyle}>
<Link href="/" underline="none" color="inherit">
<Link component={RouterLink} to="/" underline="none" color="inherit">
<Box sx={{ ...flexRowCenter }}>
<Box
component="img"
Expand Down Expand Up @@ -89,7 +90,8 @@ export default function NavBar() {
{links.map(({ label, href, external }) => (
<Link
key={label}
href={href}
component={RouterLink}
to={href}
target={external ? "_blank" : undefined}
rel={external ? "noopener" : undefined}
underline="none"
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/codeblock/CodeBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React, { useState, useEffect, useMemo } from "react";
import { Box, IconButton, Tooltip, useTheme } from "@mui/material";
import ContentCopyIcon from "@mui/icons-material/ContentCopy";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { oneLight } from "react-syntax-highlighter/dist/esm/styles/prism";
import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism";
import SyntaxHighlighter from "./syntax";
import oneLight from "react-syntax-highlighter/dist/esm/styles/prism/one-light";
import oneDark from "react-syntax-highlighter/dist/esm/styles/prism/one-dark";
import { FixedSizeList } from "react-window";
import { useContainerHeight } from "../../lib/hooks/useContainerHeight";
import type { rendererNode } from "react-syntax-highlighter";
Expand Down
17 changes: 17 additions & 0 deletions frontend/src/components/codeblock/syntax.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import SyntaxHighlighter from "react-syntax-highlighter/dist/esm/prism-light";
import bash from "react-syntax-highlighter/dist/esm/languages/prism/bash";
import c from "react-syntax-highlighter/dist/esm/languages/prism/c";
import cpp from "react-syntax-highlighter/dist/esm/languages/prism/cpp";
import json from "react-syntax-highlighter/dist/esm/languages/prism/json";
import python from "react-syntax-highlighter/dist/esm/languages/prism/python";

// Ship the languages used in GPU kernels and our documentation, rather than
// importing Prism's entire language catalogue on every leaderboard/news page.
SyntaxHighlighter.registerLanguage("bash", bash);
SyntaxHighlighter.registerLanguage("c", c);
SyntaxHighlighter.registerLanguage("cpp", cpp);
SyntaxHighlighter.alias("cpp", ["cuda", "cu"]);
SyntaxHighlighter.registerLanguage("json", json);
SyntaxHighlighter.registerLanguage("python", python);

export default SyntaxHighlighter;
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import rehypeSlug from "rehype-slug";
import remarkGfm from "remark-gfm";
import remarkMath from "remark-math";
import { useTheme } from "@mui/material/styles";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { oneDark, oneLight } from "react-syntax-highlighter/dist/esm/styles/prism";
import SyntaxHighlighter from "../codeblock/syntax";
import oneDark from "react-syntax-highlighter/dist/esm/styles/prism/one-dark";
import oneLight from "react-syntax-highlighter/dist/esm/styles/prism/one-light";
import "katex/dist/katex.min.css";

function HeadingWithAnchor({
Expand Down
11 changes: 8 additions & 3 deletions frontend/src/pages/home/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,21 @@ import {
} from "@mui/material";
import CodeIcon from "@mui/icons-material/Code";
import Grid from "@mui/material/Grid";
import { useEffect, useState } from "react";
import { lazy, Suspense, useEffect, useState } from "react";
import { useNavigate, useSearchParams } from "react-router-dom";
import { fetchLeaderboardSummaries } from "../../api/api";
import { fetcherApiCallback } from "../../lib/hooks/useApi";
import { ErrorAlert } from "../../components/alert/ErrorAlert";
import LeaderboardTile from "./components/LeaderboardTile";
import Loading from "../../components/common/loading";
import { ConstrainedContainer } from "../../components/app-layout/ConstrainedContainer";
import MarkdownRenderer from "../../components/markdown-renderer/MarkdownRenderer";
import quickStartMarkdown from "./quick-start.md?raw";
import { isExpired, getTimeLeft, shouldHideTimeRemaining } from "../../lib/date/utils";
import { ColoredSquare } from "../../components/common/ColoredSquare";
import ArrowOutwardIcon from "@mui/icons-material/ArrowOutward";

const MarkdownRenderer = lazy(() => import("../../components/markdown-renderer/MarkdownRenderer"));

interface TopUser {
rank: number;
score: number;
Expand Down Expand Up @@ -204,7 +205,11 @@ export default function Home() {
>
<DialogTitle>Submit Your First Kernel via cli tool!</DialogTitle>
<DialogContent dividers>
<MarkdownRenderer content={quickStartMarkdown} />
{isQuickStartOpen && (
<Suspense fallback={<Loading />}>
<MarkdownRenderer content={quickStartMarkdown} />
</Suspense>
)}
</DialogContent>
<DialogActions>
<Button onClick={() => setIsQuickStartOpen(false)}>Close</Button>
Expand Down
Loading
Loading