feat(patterns): tutorial system

This commit is contained in:
2025-08-07 00:41:51 +01:00
parent 3fd3021d6e
commit deb2f64ea7
15 changed files with 1386 additions and 45 deletions

View File

@@ -1,8 +1,18 @@
import type { Metadata } from "next";
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";
import { getPatternTutorial } from "@/lib/api";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { CodeBlock } from "@/components/ui/code-block";
import { Markdown } from "@/components/ui/markdown";
import { Callout } from "@/components/ui/callout";
import { Badge } from "@/components/ui/badge";
import {
CommonMistakesList,
LearningProgression,
PatternVariations,
RecognitionSignals,
RelatedPatterns,
} from "@/components/patterns";
interface PageProps {
params: Promise<{ slug: string }>;
@@ -12,7 +22,7 @@ export async function generateMetadata({ params }: PageProps): Promise<Metadata>
const { slug } = await params;
try {
const pattern = await getPattern(slug);
const pattern = await getPatternTutorial(slug);
const description =
pattern.description ||
`Learn the ${pattern.name} pattern with ${pattern.question_count} practice problems.`;
@@ -37,52 +47,142 @@ export default async function PatternDetailPage({ params }: PageProps) {
const { slug } = await params;
let pattern;
let questions;
try {
[pattern, questions] = await Promise.all([
getPattern(slug),
getQuestions({ pattern: slug, limit: 50 }),
]);
pattern = await getPatternTutorial(slug);
} catch {
notFound();
}
const difficultyLabels = ["Beginner", "Easy", "Intermediate", "Advanced", "Expert"];
const difficultyLabel = pattern.difficulty_level
? difficultyLabels[pattern.difficulty_level - 1] || "Intermediate"
: null;
return (
<div className="max-w-4xl mx-auto space-y-8">
{/* Header */}
<div>
<h1 className="text-3xl font-bold mb-2">{pattern.name}</h1>
<div className="flex items-center gap-3 mb-2">
<h1 className="text-3xl font-bold">{pattern.name}</h1>
{difficultyLabel && (
<Badge variant="outline">{difficultyLabel}</Badge>
)}
</div>
<p className="text-[var(--muted-foreground)]">
{pattern.question_count} questions using this pattern
</p>
</div>
{/* Description */}
{pattern.description && (
<Card>
<CardHeader>
<CardTitle>Description</CardTitle>
<CardTitle>What is {pattern.name}?</CardTitle>
</CardHeader>
<CardContent>{pattern.description}</CardContent>
<CardContent>
<Markdown>{pattern.description}</Markdown>
</CardContent>
</Card>
)}
{/* Metaphor - The relatable analogy */}
{pattern.metaphor && (
<Callout variant="insight" title="Think of it like this...">
<Markdown>{pattern.metaphor}</Markdown>
</Callout>
)}
{/* Core Concept - The "aha!" insight */}
{pattern.core_concept && (
<Card>
<CardHeader>
<CardTitle>Core Concept</CardTitle>
</CardHeader>
<CardContent>
<Markdown>{pattern.core_concept}</Markdown>
</CardContent>
</Card>
)}
{/* Visualization - ASCII diagram walkthrough */}
{pattern.visualization && (
<Card>
<CardHeader>
<CardTitle>Visual Walkthrough</CardTitle>
</CardHeader>
<CardContent>
<Markdown>{pattern.visualization}</Markdown>
</CardContent>
</Card>
)}
{/* Code Template */}
{pattern.code_template && (
<Card>
<CardHeader>
<CardTitle>Code Template</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm text-[var(--muted-foreground)] mb-4">
Use this skeleton as a starting point for problems using this pattern:
</p>
<CodeBlock
code={pattern.code_template}
language="python"
label="Pattern code template"
/>
</CardContent>
</Card>
)}
{/* Recognition Signals */}
{pattern.recognition_signals && pattern.recognition_signals.length > 0 && (
<RecognitionSignals signals={pattern.recognition_signals} />
)}
{/* When to Use */}
{pattern.when_to_use && (
<Card>
<CardHeader>
<CardTitle>When to Use</CardTitle>
</CardHeader>
<CardContent className="whitespace-pre-wrap">
{pattern.when_to_use}
<CardContent>
<Markdown>{pattern.when_to_use}</Markdown>
</CardContent>
</Card>
)}
<div className="space-y-4">
<h2 className="text-xl font-semibold">Questions</h2>
<div className="grid gap-4">
{questions.items.map((question) => (
<QuestionCard key={question.id} question={question} />
))}
</div>
{/* Common Mistakes */}
{pattern.common_mistakes && pattern.common_mistakes.length > 0 && (
<CommonMistakesList mistakes={pattern.common_mistakes} />
)}
{/* Pattern Variations */}
{pattern.variations && pattern.variations.length > 0 && (
<PatternVariations variations={pattern.variations} />
)}
{/* Learning Progression */}
{pattern.learning_progression && (
<LearningProgression progression={pattern.learning_progression} />
)}
{/* Related Patterns */}
<div className="grid md:grid-cols-2 gap-6">
{pattern.prerequisite_patterns && pattern.prerequisite_patterns.length > 0 && (
<RelatedPatterns
title="Prerequisites"
description="Learn these patterns first:"
patterns={pattern.prerequisite_patterns}
/>
)}
{pattern.related_patterns && pattern.related_patterns.length > 0 && (
<RelatedPatterns
title="Related Patterns"
description="Explore similar techniques:"
patterns={pattern.related_patterns}
/>
)}
</div>
</div>
);