@@ -3,6 +3,7 @@ import { useMarkdown } from '@/composables/useMarkdown'
33import { useAuth } from ' @/composables/useAuth'
44import { useAnalytics } from ' @/composables/useAnalytics'
55import { getCategoryDisplayName } from ' @/utils/categoryMeta'
6+ import type { CodingProblemOut } from ' @/types/problem'
67
78const route = useRoute ()
89const slug = route .params .slug as string
@@ -53,6 +54,9 @@ const isCompleted = ref(false)
5354const completing = ref (false )
5455const completionSuccess = ref (false )
5556const linkedQuiz = ref <{ slug: string ; title: string } | null >(null )
57+ const relatedProblems = ref <CodingProblemOut []>([])
58+
59+ const RELATED_PROBLEMS_LIMIT = 6
5660
5761const fetchCategoryLessons = async () => {
5862 if (! lesson .value ?.category ) return
@@ -82,10 +86,23 @@ const fetchLinkedQuiz = async () => {
8286 }
8387}
8488
89+ const fetchRelatedProblems = async () => {
90+ if (! lesson .value ?.category ) return
91+ try {
92+ const problems = await apiFetch <CodingProblemOut []>(
93+ ` /problems?category=${encodeURIComponent (lesson .value .category )} `
94+ )
95+ relatedProblems .value = problems
96+ } catch {
97+ // non-fatal
98+ }
99+ }
100+
85101onMounted (async () => {
86102 track (' lesson_view' , { category: lesson .value ?.category , slug })
87103 await fetchCategoryLessons ()
88104 await fetchLinkedQuiz ()
105+ await fetchRelatedProblems ()
89106})
90107
91108const prevLesson = computed <CategoryLessonInfo | null >(() => {
@@ -106,6 +123,20 @@ const categoryDisplayName = computed(() =>
106123 lesson .value ?.category ? getCategoryDisplayName (lesson .value .category ) : ' '
107124)
108125
126+ const visibleProblems = computed (() =>
127+ relatedProblems .value .slice (0 , RELATED_PROBLEMS_LIMIT )
128+ )
129+
130+ const hasMoreProblems = computed (() =>
131+ relatedProblems .value .length > RELATED_PROBLEMS_LIMIT
132+ )
133+
134+ const difficultyClasses: Record <string , string > = {
135+ easy: ' bg-green-100 text-green-700' ,
136+ medium: ' bg-yellow-100 text-yellow-700' ,
137+ hard: ' bg-red-100 text-red-700' ,
138+ }
139+
109140async function markComplete() {
110141 if (! lesson .value || completing .value || isCompleted .value ) return
111142 completing .value = true
@@ -165,6 +196,38 @@ const renderedContent = computed(() =>
165196 :initial-rating =" lesson.user_rating ?? null"
166197 />
167198
199+ <!-- Related Problems -->
200+ <div v-if =" relatedProblems.length > 0" class =" border-t border-gray-200 pt-8 mb-8" >
201+ <h2 class =" text-lg font-semibold text-gray-900 mb-4" >Related Problems</h2 >
202+ <ul class =" space-y-2" >
203+ <li
204+ v-for =" problem in visibleProblems"
205+ :key =" problem.id"
206+ class =" flex items-center gap-3"
207+ >
208+ <NuxtLink
209+ :to =" `/problems/${problem.id}`"
210+ class =" text-purple-700 hover:text-purple-900 font-medium hover:underline"
211+ >
212+ {{ problem.title }}
213+ </NuxtLink >
214+ <span
215+ class =" inline-block px-2 py-0.5 text-xs font-semibold rounded capitalize"
216+ :class =" difficultyClasses[problem.difficulty] ?? 'bg-gray-100 text-gray-600'"
217+ >
218+ {{ problem.difficulty }}
219+ </span >
220+ </li >
221+ </ul >
222+ <NuxtLink
223+ v-if =" hasMoreProblems"
224+ :to =" `/problems?category=${lesson.category}`"
225+ class =" inline-block mt-3 text-sm text-purple-700 hover:text-purple-900 hover:underline"
226+ >
227+ See all {{ relatedProblems.length }} problems &rarr ;
228+ </NuxtLink >
229+ </div >
230+
168231 <!-- Completion section -->
169232 <div class =" border-t border-gray-200 pt-8" >
170233 <!-- Quiz exists: quiz is the completion mechanism -->
0 commit comments