Skip to content

Commit 04869e2

Browse files
Created prototype website (#6)
1 parent a8217e1 commit 04869e2

50 files changed

Lines changed: 10166 additions & 3513 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,7 @@ yarn-error.log*
3939
# typescript
4040
*.tsbuildinfo
4141
next-env.d.ts
42+
43+
# playwright
44+
/test-results/
45+
/playwright-report/

e2e/auth-flow.spec.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { expect, test } from "@playwright/test";
2+
3+
test("user can login and open problems page", async ({ page }) => {
4+
await page.goto("/login");
5+
6+
await expect(page.getByRole("heading", { name: /sign in to continue/i })).toBeVisible();
7+
8+
await page.getByLabel("Email").fill("playwright@example.com");
9+
await page.getByLabel("Password").fill("password123");
10+
await page.getByRole("button", { name: "Sign in" }).click();
11+
12+
await expect(page).toHaveURL(/\/problems/);
13+
await expect(page.getByRole("heading", { name: /^problems$/i })).toBeVisible();
14+
});

e2e/navigation-pages.spec.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { expect, test } from "@playwright/test";
2+
3+
test("all primary pages load and are not blank", async ({ page }) => {
4+
await page.goto("/login");
5+
6+
await page.getByLabel("Email").fill("navigation@example.com");
7+
await page.getByLabel("Password").fill("password123");
8+
await page.getByRole("button", { name: "Sign in" }).click();
9+
10+
const checks = [
11+
{ link: "Dashboard", pathSuffix: "/", heading: /^dashboard$/i },
12+
{ link: "Problems", pathSuffix: "/problems", heading: /^problems$/i },
13+
{
14+
link: "Competitions",
15+
pathSuffix: "/competitions",
16+
heading: /^competitions$/i,
17+
},
18+
{ link: "Learn", pathSuffix: "/learn", heading: /^learn$/i },
19+
{ link: "Progress", pathSuffix: "/progress", heading: /^progress$/i },
20+
];
21+
22+
for (const item of checks) {
23+
await page.getByRole("link", { name: item.link }).click();
24+
await expect(page).toHaveURL(new RegExp(`${item.pathSuffix}$`));
25+
await expect(page.getByRole("heading", { name: item.heading })).toBeVisible();
26+
}
27+
});
28+
29+
test("problem arena route opens from problems list", async ({ page }) => {
30+
await page.goto("/login");
31+
32+
await page.getByLabel("Email").fill("arena@example.com");
33+
await page.getByLabel("Password").fill("password123");
34+
await page.getByRole("button", { name: "Sign in" }).click();
35+
36+
await page.getByRole("link", { name: "Problems" }).click();
37+
await page.getByText("KNN Classifier on Iris", { exact: false }).first().click();
38+
39+
await expect(page).toHaveURL(/\/problems\/knn-classifier-iris/);
40+
await expect(
41+
page.getByRole("heading", { name: /knn classifier on iris/i })
42+
).toBeVisible();
43+
});
44+
45+
test("topics in sidebar navigate to filtered problems", async ({ page }) => {
46+
await page.goto("/login");
47+
48+
await page.getByLabel("Email").fill("topics@example.com");
49+
await page.getByLabel("Password").fill("password123");
50+
await page.getByRole("button", { name: "Sign in" }).click();
51+
52+
await page.getByRole("link", { name: "Learn" }).click();
53+
await page.getByRole("button", { name: "Data Preprocessing" }).click();
54+
55+
await expect(page).toHaveURL(/\/problems\?category=Data\+Preprocessing$/);
56+
await expect(page.getByRole("heading", { name: /^problems$/i })).toBeVisible();
57+
});

instrumentation-client.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import * as Sentry from "@sentry/nextjs";
2+
3+
Sentry.init({
4+
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
5+
tracesSampleRate: Number(process.env.NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE || 0.1),
6+
environment: process.env.NEXT_PUBLIC_APP_ENV || process.env.NODE_ENV,
7+
enabled: Boolean(process.env.NEXT_PUBLIC_SENTRY_DSN),
8+
integrations: (integrations) => integrations,
9+
});

instrumentation.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export async function register() {
2+
if (process.env.NEXT_RUNTIME === "nodejs") {
3+
await import("./sentry.server.config");
4+
}
5+
6+
if (process.env.NEXT_RUNTIME === "edge") {
7+
await import("./sentry.edge.config");
8+
}
9+
}

middleware.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { NextRequest, NextResponse } from "next/server";
2+
import { AUTH_COOKIE_NAME } from "./src/lib/auth";
3+
4+
function isAuthenticated(request: NextRequest): boolean {
5+
return request.cookies.has(AUTH_COOKIE_NAME);
6+
}
7+
8+
export function middleware(request: NextRequest) {
9+
const { pathname, search } = request.nextUrl;
10+
const authenticated = isAuthenticated(request);
11+
12+
if (pathname === "/login" && authenticated) {
13+
const problemsUrl = new URL("/problems", request.url);
14+
return NextResponse.redirect(problemsUrl);
15+
}
16+
17+
if (!authenticated && pathname !== "/login") {
18+
const loginUrl = new URL("/login", request.url);
19+
const redirectPath = `${pathname}${search}`;
20+
loginUrl.searchParams.set("redirect", redirectPath);
21+
return NextResponse.redirect(loginUrl);
22+
}
23+
24+
return NextResponse.next();
25+
}
26+
27+
export const config = {
28+
matcher: [
29+
"/",
30+
"/login",
31+
"/problems/:path*",
32+
"/competitions/:path*",
33+
"/learn/:path*",
34+
"/tracks/:path*",
35+
"/progress/:path*",
36+
"/profile/:path*",
37+
],
38+
};

next.config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import type { NextConfig } from "next";
33
const nextConfig: NextConfig = {
44
/* config options here */
55
reactCompiler: true,
6+
allowedDevOrigins: ["127.0.0.1"],
7+
turbopack: {
8+
root: __dirname,
9+
},
610
};
711

812
export default nextConfig;

0 commit comments

Comments
 (0)