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
2 changes: 1 addition & 1 deletion frontend/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ export async function submitCode(

const details = payloadData?.details as Record<string, unknown> | undefined;
const sub_id = (details?.id as number) || 0;
console.log("Submission successful with sub_id", sub_id);
console.log("Submission successful with details", details);
return {
sub_id
};
Expand Down
34 changes: 33 additions & 1 deletion frontend/src/pages/home/Home.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,20 @@ import { vi, expect, it, describe, beforeEach } from "vitest";
// Mock the API hook
vi.mock("../../lib/hooks/useApi");

// Mock react-syntax-highlighter to avoid ESM issues
vi.mock("react-syntax-highlighter", () => ({
default: ({ children }: { children: string }) => <pre>{children}</pre>,
Prism: ({ children }: { children: string }) => <pre>{children}</pre>,
}));

vi.mock("react-syntax-highlighter/dist/esm/styles/prism", () => ({
vscDarkPlus: {},
}));

// Mock utility functions
vi.mock("../../lib/date/utils", () => ({
getTimeLeft: vi.fn(() => "2 days 5 hours remaining"),
isExpired: vi.fn(() => false),
}));

vi.mock("../../lib/utils/ranking", () => ({
Expand Down Expand Up @@ -41,6 +52,7 @@ describe("Home", () => {
const mockHookReturn = {
data: null,
loading: true,
hasLoaded: false,
error: null,
errorStatus: null,
call: mockCall,
Expand All @@ -52,13 +64,18 @@ describe("Home", () => {

renderWithProviders(<Home />);

expect(screen.getByText(/Summoning/i)).toBeInTheDocument();
// Page structure is visible during loading
expect(screen.getByText("Leaderboards")).toBeInTheDocument();
expect(screen.getByText("Submit your first kernel")).toBeInTheDocument();
// Loading indicator is present
expect(screen.getByRole("progressbar")).toBeInTheDocument();
});

it("shows error message", () => {
const mockHookReturn = {
data: null,
loading: false,
hasLoaded: true,
error: "Something went wrong",
errorStatus: 500,
call: mockCall,
Expand All @@ -78,6 +95,7 @@ describe("Home", () => {
const mockHookReturn = {
data: null,
loading: false,
hasLoaded: true,
error: "Network error",
errorStatus: null,
call: mockCall,
Expand Down Expand Up @@ -117,6 +135,7 @@ describe("Home", () => {
const mockHookReturn = {
data: mockData,
loading: false,
hasLoaded: true,
error: null,
errorStatus: null,
call: mockCall,
Expand All @@ -143,6 +162,7 @@ describe("Home", () => {
const mockHookReturn = {
data: mockData,
loading: false,
hasLoaded: true,
error: null,
errorStatus: null,
call: mockCall,
Expand Down Expand Up @@ -202,6 +222,7 @@ describe("Home", () => {
const mockHookReturn = {
data: mockData,
loading: false,
hasLoaded: true,
error: null,
errorStatus: null,
call: mockCall,
Expand Down Expand Up @@ -240,6 +261,7 @@ describe("Home", () => {
const mockHookReturn = {
data: mockData,
loading: false,
hasLoaded: true,
error: null,
errorStatus: null,
call: mockCall,
Expand Down Expand Up @@ -273,6 +295,7 @@ describe("Home", () => {
const mockHookReturn = {
data: mockData,
loading: false,
hasLoaded: true,
error: null,
errorStatus: null,
call: mockCall,
Expand Down Expand Up @@ -313,6 +336,7 @@ describe("Home", () => {
const mockHookReturn = {
data: mockData,
loading: false,
hasLoaded: true,
error: null,
errorStatus: null,
call: mockCall,
Expand Down Expand Up @@ -351,6 +375,7 @@ describe("Home", () => {
const mockHookReturn = {
data: mockData,
loading: false,
hasLoaded: true,
error: null,
errorStatus: null,
call: mockCall,
Expand Down Expand Up @@ -399,6 +424,7 @@ describe("Home", () => {
const mockHookReturn = {
data: mockData,
loading: false,
hasLoaded: true,
error: null,
errorStatus: null,
call: mockCall,
Expand Down Expand Up @@ -439,6 +465,7 @@ describe("Home", () => {
const mockHookReturn = {
data: mockData,
loading: false,
hasLoaded: true,
error: null,
errorStatus: null,
call: mockCall,
Expand Down Expand Up @@ -478,6 +505,7 @@ describe("Home", () => {
const mockHookReturn = {
data: mockData,
loading: false,
hasLoaded: true,
error: null,
errorStatus: null,
call: mockCall,
Expand Down Expand Up @@ -528,6 +556,7 @@ describe("Home", () => {
const mockHookReturn = {
data: mockData,
loading: false,
hasLoaded: true,
error: null,
errorStatus: null,
call: mockCall,
Expand Down Expand Up @@ -568,6 +597,7 @@ describe("Home", () => {
const mockHookReturn = {
data: mockData,
loading: false,
hasLoaded: true,
error: null,
errorStatus: null,
call: mockCall,
Expand Down Expand Up @@ -610,6 +640,7 @@ describe("Home", () => {
const mockHookReturn = {
data: mockData,
loading: false,
hasLoaded: true,
error: null,
errorStatus: null,
call: mockCall,
Expand Down Expand Up @@ -649,6 +680,7 @@ describe("Home", () => {
const mockHookReturn = {
data: mockData,
loading: false,
hasLoaded: true,
error: null,
errorStatus: null,
call: mockCall,
Expand Down
139 changes: 125 additions & 14 deletions frontend/src/pages/home/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@ import {
DialogContent,
DialogTitle,
Typography,
List,
ListItemButton,
ListItemText,
Stack,
Chip,
} from "@mui/material";
import CodeIcon from "@mui/icons-material/Code";
import Grid from "@mui/material/Grid";
import { useEffect, useState } from "react";
import { useSearchParams } from "react-router-dom";
import { useNavigate, useSearchParams } from "react-router-dom";
import { fetchLeaderboardSummaries } from "../../api/api";
import { fetcherApiCallback } from "../../lib/hooks/useApi";
import { ErrorAlert } from "../../components/alert/ErrorAlert";
Expand All @@ -18,6 +24,8 @@ 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 } from "../../lib/date/utils";
import { ColoredSquare } from "../../components/common/ColoredSquare";

interface TopUser {
rank: number;
Expand All @@ -41,7 +49,9 @@ interface LeaderboardSummaries {

export default function Home() {
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const [isQuickStartOpen, setIsQuickStartOpen] = useState(false);
const [isLeaderboardSelectOpen, setIsLeaderboardSelectOpen] = useState(false);
const useBeta = searchParams.has("use_beta");
const forceRefresh = searchParams.has("force_refresh");

Expand All @@ -59,36 +69,137 @@ export default function Home() {
}, [call, useBeta, forceRefresh]);

const leaderboards = data?.leaderboards || [];
const activeLeaderboards = leaderboards.filter(
(lb) => !isExpired(lb.deadline)
);

const handleLeaderboardSelect = (id: number) => {
setIsLeaderboardSelectOpen(false);
navigate(`/leaderboard/${id}/editor`);
};

return (
<ConstrainedContainer>
<Box>
<Typography variant="h1" component="h1" sx={{ mb: 3 }}>
Leaderboards
</Typography>

<Box sx={{ mb: 4 }}>
<Button
variant="contained"
onClick={() => setIsQuickStartOpen(true)}
sx={{
textTransform: "none",
fontWeight: 500,
px: 3,
py: 1.5,
}}
>
<Typography variant="h6" sx={{ mb: 1.5 }}>
Submit your first kernel
</Button>
</Typography>
<Stack direction={{ xs: "column", sm: "row" }} spacing={2}>
<Button
variant="contained"
startIcon={<CodeIcon />}
onClick={() => setIsLeaderboardSelectOpen(true)}
sx={{
textTransform: "none",
fontWeight: 500,
px: 3,
py: 1.5,
}}
>
Submit via browser
<Chip
label="beta"
size="small"
sx={{
ml: 1,
height: 20,
fontSize: "0.7rem",
bgcolor: "warning.main",
color: "warning.contrastText",
}}
/>
</Button>
<Button
variant="outlined"
onClick={() => setIsQuickStartOpen(true)}
sx={{
textTransform: "none",
fontWeight: 500,
px: 3,
py: 1.5,
}}
>
Submit via cli
</Button>
</Stack>
</Box>

{/* Leaderboard Selection Dialog */}
<Dialog
open={isLeaderboardSelectOpen}
onClose={() => setIsLeaderboardSelectOpen(false)}
maxWidth="sm"
fullWidth
sx={{
"& .MuiDialog-paper": {
maxHeight: { xs: "80vh", sm: "70vh" },
},
}}
>
<DialogTitle>Select an active leaderboard</DialogTitle>
<DialogContent dividers sx={{ p: 0 }}>
{activeLeaderboards.length > 0 ? (
<List sx={{ width: "100%", py: 0 }}>
{activeLeaderboards.map((lb) => (
<ListItemButton
key={lb.id}
onClick={() => handleLeaderboardSelect(lb.id)}
sx={{
py: 1,
px: { xs: 1.5, sm: 2 },
borderBottom: "1px solid",
borderColor: "divider",
"&:last-child": { borderBottom: "none" },
}}
>
<ListItemText
primary={
<Box sx={{ display: "flex", alignItems: "center" }}>
<ColoredSquare name={lb.name} />
{lb.name}
</Box>
}
secondary={getTimeLeft(lb.deadline)}
slotProps={{
primary: {
fontWeight: 500,
fontSize: { xs: "0.9rem", sm: "0.95rem" },
component: "div",
},
secondary: {
fontSize: "0.8rem",
},
}}
/>
</ListItemButton>
))}
</List>
) : (
<Box sx={{ p: 3, textAlign: "center" }}>
<Typography color="text.secondary">
No active leaderboards available.
</Typography>
</Box>
)}
</DialogContent>
<DialogActions>
<Button onClick={() => setIsLeaderboardSelectOpen(false)}>
Cancel
</Button>
</DialogActions>
</Dialog>

<Dialog
open={isQuickStartOpen}
onClose={() => setIsQuickStartOpen(false)}
maxWidth="md"
fullWidth
>
<DialogTitle>Submit Your First Kernel</DialogTitle>
<DialogTitle>Submit Your First Kernel via cli tool!</DialogTitle>
<DialogContent dividers>
<MarkdownRenderer content={quickStartMarkdown} />
</DialogContent>
Expand Down
Loading
Loading