diff --git a/frontend/src/app/categories/page.tsx b/frontend/src/app/categories/page.tsx
new file mode 100644
index 0000000..f0ef69a
--- /dev/null
+++ b/frontend/src/app/categories/page.tsx
@@ -0,0 +1,37 @@
+import { getCategories } from "@/lib/api";
+import Link from "next/link";
+
+export default async function CategoriesPage() {
+ const { items: categories } = await getCategories();
+
+ return (
+
+
Categories
+
+ Browse questions by data structure or algorithm category.
+
+
+
+ {categories.map((category) => (
+
+
+
{category.name}
+
+ {category.question_count} questions
+
+
+ {category.description && (
+
+ {category.description}
+
+ )}
+
+ ))}
+
+
+ );
+}
diff --git a/frontend/src/app/error.tsx b/frontend/src/app/error.tsx
new file mode 100644
index 0000000..f8e9f52
--- /dev/null
+++ b/frontend/src/app/error.tsx
@@ -0,0 +1,23 @@
+"use client";
+
+export default function Error({
+ reset,
+}: {
+ error: Error & { digest?: string };
+ reset: () => void;
+}) {
+ return (
+
+
Something went wrong
+
+ An error occurred while loading this page.
+
+
+
+ );
+}
diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css
new file mode 100644
index 0000000..e688465
--- /dev/null
+++ b/frontend/src/app/globals.css
@@ -0,0 +1,40 @@
+@import "tailwindcss";
+
+:root {
+ --background: #ffffff;
+ --foreground: #171717;
+ --card: #ffffff;
+ --card-foreground: #171717;
+ --primary: #3b82f6;
+ --primary-foreground: #ffffff;
+ --secondary: #f3f4f6;
+ --secondary-foreground: #171717;
+ --muted: #f3f4f6;
+ --muted-foreground: #6b7280;
+ --border: #e5e7eb;
+ --ring: #3b82f6;
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ --background: #0a0a0a;
+ --foreground: #ededed;
+ --card: #171717;
+ --card-foreground: #ededed;
+ --primary: #3b82f6;
+ --primary-foreground: #ffffff;
+ --secondary: #262626;
+ --secondary-foreground: #ededed;
+ --muted: #262626;
+ --muted-foreground: #a3a3a3;
+ --border: #262626;
+ --ring: #3b82f6;
+ }
+}
+
+body {
+ background: var(--background);
+ color: var(--foreground);
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
+ "Helvetica Neue", Arial, sans-serif;
+}
diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx
new file mode 100644
index 0000000..3c77fb9
--- /dev/null
+++ b/frontend/src/app/layout.tsx
@@ -0,0 +1,59 @@
+import type { Metadata } from "next";
+import Link from "next/link";
+import { Providers } from "./providers";
+import "./globals.css";
+
+export const metadata: Metadata = {
+ title: "CodeTutor - Coding Interview Preparation",
+ description:
+ "Master coding interviews with curated questions, detailed explanations, and optimal solutions.",
+};
+
+export default function RootLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+
+
+
+
+ {children}
+
+
+
+
+ );
+}
diff --git a/frontend/src/app/not-found.tsx b/frontend/src/app/not-found.tsx
new file mode 100644
index 0000000..d32f5e8
--- /dev/null
+++ b/frontend/src/app/not-found.tsx
@@ -0,0 +1,18 @@
+import Link from "next/link";
+
+export default function NotFound() {
+ return (
+
+
404
+
+ The page you're looking for doesn't exist.
+
+
+ Go Home
+
+
+ );
+}
diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx
new file mode 100644
index 0000000..f833b47
--- /dev/null
+++ b/frontend/src/app/page.tsx
@@ -0,0 +1,103 @@
+import Link from "next/link";
+import { getStats } from "@/lib/api";
+
+export default async function HomePage() {
+ let stats;
+ try {
+ stats = await getStats();
+ } catch {
+ stats = null;
+ }
+
+ return (
+
+
+ Master Coding Interviews
+
+ Curated collection of coding interview questions with detailed
+ explanations, common pitfalls, and optimal solutions.
+
+
+ Browse Questions
+
+
+
+ {stats && (
+
+
+
+ {stats.total_questions}
+
+
+ Total Questions
+
+
+
+
+ {stats.by_category.length}
+
+
Categories
+
+
+
+ {stats.by_pattern.length}
+
+
+ Algorithmic Patterns
+
+
+
+ )}
+
+ {stats && (
+
+
+
+ {stats.by_difficulty.easy}
+
+
Easy
+
+
+
+ {stats.by_difficulty.medium}
+
+
Medium
+
+
+
+ {stats.by_difficulty.hard}
+
+
Hard
+
+
+ )}
+
+
+ Quick Links
+
+
+
Browse by Category
+
+ Arrays, Trees, Graphs, Dynamic Programming, and more
+
+
+
+
Browse by Pattern
+
+ Two Pointers, Sliding Window, BFS, DFS, Backtracking, and more
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/app/patterns/[slug]/page.tsx b/frontend/src/app/patterns/[slug]/page.tsx
new file mode 100644
index 0000000..8f51f15
--- /dev/null
+++ b/frontend/src/app/patterns/[slug]/page.tsx
@@ -0,0 +1,63 @@
+import { getPattern, getQuestions } from "@/lib/api";
+import { QuestionCard } from "@/components/questions/question-card";
+import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
+import { notFound } from "next/navigation";
+
+export default async function PatternDetailPage({
+ params,
+}: {
+ params: Promise<{ slug: string }>;
+}) {
+ const { slug } = await params;
+
+ let pattern;
+ let questions;
+ try {
+ [pattern, questions] = await Promise.all([
+ getPattern(slug),
+ getQuestions({ pattern: slug, limit: 50 }),
+ ]);
+ } catch {
+ notFound();
+ }
+
+ return (
+
+
+
{pattern.name}
+
+ {pattern.question_count} questions using this pattern
+
+
+
+ {pattern.description && (
+
+
+ Description
+
+ {pattern.description}
+
+ )}
+
+ {pattern.when_to_use && (
+
+
+ When to Use
+
+
+ {pattern.when_to_use}
+
+
+ )}
+
+
+
Questions
+
+ {questions.items.map((question) => (
+
+ ))}
+
+
+
+ );
+}
diff --git a/frontend/src/app/patterns/page.tsx b/frontend/src/app/patterns/page.tsx
new file mode 100644
index 0000000..6d9d5a0
--- /dev/null
+++ b/frontend/src/app/patterns/page.tsx
@@ -0,0 +1,38 @@
+import { getPatterns } from "@/lib/api";
+import Link from "next/link";
+
+export default async function PatternsPage() {
+ const { items: patterns } = await getPatterns();
+
+ return (
+
+
Algorithmic Patterns
+
+ Master common problem-solving patterns to recognize and apply them in
+ interviews.
+
+
+
+ {patterns.map((pattern) => (
+
+
+
{pattern.name}
+
+ {pattern.question_count} questions
+
+
+ {pattern.description && (
+
+ {pattern.description}
+
+ )}
+
+ ))}
+
+
+ );
+}
diff --git a/frontend/src/app/providers.tsx b/frontend/src/app/providers.tsx
new file mode 100644
index 0000000..8761383
--- /dev/null
+++ b/frontend/src/app/providers.tsx
@@ -0,0 +1,22 @@
+"use client";
+
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { useState, type ReactNode } from "react";
+
+export function Providers({ children }: { children: ReactNode }) {
+ const [queryClient] = useState(
+ () =>
+ new QueryClient({
+ defaultOptions: {
+ queries: {
+ staleTime: 60 * 1000,
+ refetchOnWindowFocus: false,
+ },
+ },
+ })
+ );
+
+ return (
+ {children}
+ );
+}
diff --git a/frontend/src/app/questions/[slug]/page.tsx b/frontend/src/app/questions/[slug]/page.tsx
new file mode 100644
index 0000000..dc4d31c
--- /dev/null
+++ b/frontend/src/app/questions/[slug]/page.tsx
@@ -0,0 +1,229 @@
+import { getQuestion } from "@/lib/api";
+import { Badge } from "@/components/ui/badge";
+import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
+import { CodeBlock } from "@/components/ui/code-block";
+import { getDifficultyColor, capitalize } from "@/lib/utils";
+import Link from "next/link";
+import { notFound } from "next/navigation";
+
+export default async function QuestionDetailPage({
+ params,
+}: {
+ params: Promise<{ slug: string }>;
+}) {
+ const { slug } = await params;
+
+ let question;
+ try {
+ question = await getQuestion(slug);
+ } catch {
+ notFound();
+ }
+
+ return (
+
+
+
+
{question.title}
+
+ {capitalize(question.difficulty)}
+
+
+
+
+ {question.categories.map((cat) => (
+
+ {cat.name}
+
+ ))}
+ {question.patterns.map((pat) => (
+
+ {pat.name}
+
+ ))}
+
+
+ {question.leetcode_url && (
+
+ View on LeetCode
+
+ )}
+
+
+
+
+ Problem
+
+
+ {question.description}
+
+
+
+ {question.constraints && (
+
+
+ Constraints
+
+
+
+ {question.constraints}
+
+
+
+ )}
+
+ {question.examples && question.examples.length > 0 && (
+
+
+ Examples
+
+
+ {question.examples.map((example, i) => (
+
+
+ Input:
+ {example.input}
+
+
+ Output:
+ {example.output}
+
+ {example.explanation && (
+
+ {example.explanation}
+
+ )}
+
+ ))}
+
+
+ )}
+
+ {question.explanation && (
+ <>
+
+
+ Approach
+
+
+ {question.explanation.approach}
+
+
+
+
+
+ Intuition
+
+
+ {question.explanation.intuition}
+
+
+
+
+
+ Complexity Analysis
+
+
+
+ Time:
+ {question.explanation.time_complexity}
+
+
+ Space:
+ {question.explanation.space_complexity}
+
+ {question.explanation.complexity_explanation && (
+
+ {question.explanation.complexity_explanation}
+
+ )}
+
+
+
+ {question.explanation.common_pitfalls &&
+ question.explanation.common_pitfalls.length > 0 && (
+
+
+ Common Pitfalls
+
+
+ {question.explanation.common_pitfalls.map((pitfall, i) => (
+
+
{pitfall.title}
+
+ {pitfall.description}
+
+ {pitfall.wrong_approach && (
+
+ Wrong:
+ {pitfall.wrong_approach}
+
+ )}
+ {pitfall.correct_approach && (
+
+ Correct:
+ {pitfall.correct_approach}
+
+ )}
+
+ ))}
+
+
+ )}
+
+ {question.explanation.key_takeaways &&
+ question.explanation.key_takeaways.length > 0 && (
+
+
+ Key Takeaways
+
+
+
+ {question.explanation.key_takeaways.map((takeaway, i) => (
+ - {takeaway}
+ ))}
+
+
+
+ )}
+ >
+ )}
+
+ {question.solutions.length > 0 && (
+
+
+ Solutions
+
+
+ {question.solutions.map((solution) => (
+
+
+
{solution.approach_name}
+ {solution.is_optimal && (
+
+ Optimal
+
+ )}
+
+ {solution.explanation && (
+
+ {solution.explanation}
+
+ )}
+
+
+ ))}
+
+
+ )}
+
+ );
+}
diff --git a/frontend/src/app/questions/page.tsx b/frontend/src/app/questions/page.tsx
new file mode 100644
index 0000000..e614e3f
--- /dev/null
+++ b/frontend/src/app/questions/page.tsx
@@ -0,0 +1,159 @@
+import { getQuestions, getCategories, getPatterns } from "@/lib/api";
+import { QuestionCard } from "@/components/questions/question-card";
+import Link from "next/link";
+
+interface SearchParams {
+ difficulty?: string;
+ category?: string;
+ pattern?: string;
+ search?: string;
+ page?: string;
+}
+
+export default async function QuestionsPage({
+ searchParams,
+}: {
+ searchParams: Promise;
+}) {
+ const params = await searchParams;
+ const [questionsResponse, categoriesResponse, patternsResponse] =
+ await Promise.all([
+ getQuestions({
+ difficulty: params.difficulty,
+ category: params.category,
+ pattern: params.pattern,
+ search: params.search,
+ page: params.page ? parseInt(params.page) : 1,
+ }),
+ getCategories(),
+ getPatterns(),
+ ]);
+
+ const difficulties = ["easy", "medium", "hard"];
+
+ return (
+
+
Questions
+
+
+
+
+
+
+ All
+
+ {difficulties.map((d) => (
+
+ {d}
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Showing {questionsResponse.items.length} of {questionsResponse.total}{" "}
+ questions
+
+
+
+ {questionsResponse.items.map((question) => (
+
+ ))}
+
+
+ {questionsResponse.pages > 1 && (
+
+ {Array.from({ length: questionsResponse.pages }, (_, i) => i + 1).map(
+ (page) => (
+
+ {page}
+
+ )
+ )}
+
+ )}
+
+ );
+}