diff --git a/frontend/src/components/questions/question-detail.tsx b/frontend/src/components/questions/question-detail.tsx
new file mode 100644
index 0000000..d0cdefa
--- /dev/null
+++ b/frontend/src/components/questions/question-detail.tsx
@@ -0,0 +1,282 @@
+import { Badge } from "@/components/ui/badge";
+import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
+import { CodeBlock } from "@/components/ui/code-block";
+import { Markdown } from "@/components/ui/markdown";
+import { Callout, ApproachBox } from "@/components/ui/callout";
+import { Collapsible } from "@/components/ui/collapsible";
+import { getDifficultyVariant, getDifficultyLabel, capitalize } from "@/lib/utils";
+import {
+ FileText,
+ AlertCircle,
+ BookOpen,
+ Code,
+ Clock,
+ HardDrive,
+} from "lucide-react";
+import Link from "next/link";
+import type { QuestionDetail as QuestionDetailType } from "@/types";
+
+interface QuestionDetailProps {
+ question: QuestionDetailType;
+}
+
+export function QuestionDetail({ question }: QuestionDetailProps) {
+ const optimalSolutions = question.solutions.filter((s) => s.is_optimal);
+ const otherSolutions = question.solutions.filter((s) => !s.is_optimal);
+
+ 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 && (
+ <>
+
+
+ {question.explanation.approach}
+
+
+
+
+
+ {question.explanation.intuition}
+
+
+
+
+
+
+
+ Complexity Analysis
+
+
+
+
+
+
+ Time Complexity:
+ {question.explanation.time_complexity}
+
+
+
+
+
+ Space Complexity:
+ {question.explanation.space_complexity}
+
+
+
+
+
+ {question.explanation.common_pitfalls &&
+ question.explanation.common_pitfalls.length > 0 && (
+
+
+ {question.explanation.common_pitfalls.map((pitfall, i) => (
+
+
{pitfall.title}
+
+ {pitfall.description}
+
+ {(pitfall.wrong_approach || pitfall.correct_approach) && (
+
+ {pitfall.wrong_approach && (
+
+
+ {pitfall.wrong_approach}
+
+
+ )}
+ {pitfall.correct_approach && (
+
+
+ {pitfall.correct_approach}
+
+
+ )}
+
+ )}
+
+ ))}
+
+
+ )}
+
+ {question.explanation.key_takeaways &&
+ question.explanation.key_takeaways.length > 0 && (
+
+
+ {question.explanation.key_takeaways.map((takeaway, i) => (
+ -
+ {takeaway}
+
+ ))}
+
+
+ )}
+ >
+ )}
+
+ {question.solutions.length > 0 && (
+
+
+
+
+ Solutions
+
+
+
+ {optimalSolutions.map((solution) => (
+
+
+
{solution.approach_name}
+ Optimal
+
+ {solution.explanation && (
+
+ {solution.explanation}
+
+ )}
+
+
+ ))}
+
+ {otherSolutions.length > 0 && (
+
+ {otherSolutions.map((solution) => (
+
+ {solution.approach_name}
+
+ Alternative
+
+
+ }
+ defaultOpen={false}
+ >
+ {solution.explanation && (
+
+ {solution.explanation}
+
+ )}
+
+
+ ))}
+
+ )}
+
+
+ )}
+
+ );
+}
diff --git a/frontend/src/components/ui/code-block.tsx b/frontend/src/components/ui/code-block.tsx
index c2b200c..2b6c849 100644
--- a/frontend/src/components/ui/code-block.tsx
+++ b/frontend/src/components/ui/code-block.tsx
@@ -1,37 +1,88 @@
"use client";
-import { useState } from "react";
+import { useState, useCallback } from "react";
+import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
+import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism";
interface CodeBlockProps {
code: string;
language?: string;
+ label?: string;
}
-export function CodeBlock({ code, language = "python" }: CodeBlockProps) {
+export function CodeBlock({
+ code,
+ language = "python",
+ label,
+}: CodeBlockProps) {
const [copied, setCopied] = useState(false);
- const handleCopy = async () => {
+ const handleCopy = useCallback(async () => {
await navigator.clipboard.writeText(code);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
+ }, [code]);
+
+ const handleKeyDown = useCallback(
+ (e: React.KeyboardEvent) => {
+ if (e.key === "Enter" || e.key === " ") {
+ e.preventDefault();
+ handleCopy();
+ }
+ },
+ [handleCopy]
+ );
+
+ const codeLabel = label || `${language} code example`;
+
+ // Map common language names to Prism language identifiers
+ const languageMap: Record = {
+ python: "python",
+ javascript: "javascript",
+ typescript: "typescript",
+ java: "java",
+ cpp: "cpp",
+ c: "c",
+ go: "go",
+ rust: "rust",
};
+ const prismLanguage = languageMap[language.toLowerCase()] || language;
+
return (
-
+
+
+ {copied ? "Code copied to clipboard" : ""}
+
-
+
{language}
-
- {code}
-
+
+
+ {code.trim()}
+
+
);
}
diff --git a/frontend/src/components/ui/markdown.tsx b/frontend/src/components/ui/markdown.tsx
new file mode 100644
index 0000000..db6feb1
--- /dev/null
+++ b/frontend/src/components/ui/markdown.tsx
@@ -0,0 +1,62 @@
+"use client";
+
+import ReactMarkdown from "react-markdown";
+import rehypeRaw from "rehype-raw";
+
+interface MarkdownProps {
+ children: string;
+ className?: string;
+}
+
+export function Markdown({ children, className = "" }: MarkdownProps) {
+ return (
+
+
{
+ return (
+
+ {children}
+
+ );
+ },
+ // Style code blocks
+ pre: ({ children }) => {
+ return (
+
+ {children}
+
+ );
+ },
+ // Style unordered lists
+ ul: ({ children }) => {
+ return (
+
+ );
+ },
+ // Style ordered lists
+ ol: ({ children }) => {
+ return (
+
+ {children}
+
+ );
+ },
+ // Style paragraphs with spacing
+ p: ({ children }) => {
+ return {children}
;
+ },
+ }}
+ >
+ {children}
+
+
+ );
+}