From 33e9e281d0dcc85e8cfe71ff50fc778e5f051822 Mon Sep 17 00:00:00 2001 From: Kai Chappell Date: Fri, 20 Jun 2025 13:53:44 +0100 Subject: [PATCH] feat(frontend): markdown and detail components --- .../components/questions/question-detail.tsx | 282 ++++++++++++++++++ frontend/src/components/ui/code-block.tsx | 69 ++++- frontend/src/components/ui/markdown.tsx | 62 ++++ 3 files changed, 404 insertions(+), 9 deletions(-) create mode 100644 frontend/src/components/questions/question-detail.tsx create mode 100644 frontend/src/components/ui/markdown.tsx 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 + + )} +
+ + + + + + + + {question.description} + + + + {question.constraints && ( + + + + + + + {question.constraints} + + + )} + + {question.examples && question.examples.length > 0 && ( + + + + + + + {question.examples.map((example, i) => ( +
+
+ Input: + {example.input} +
+
+ Output: + {example.output} +
+ {example.explanation && ( +
+ {example.explanation} +
+ )} +
+ ))} +
+
+ )} + + {question.explanation && ( + <> + +
+ {question.explanation.approach} +
+
+ + +
+ {question.explanation.intuition} +
+
+ + + + + + + +
+
+
+
+
+
+ + {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 && ( + + + +