From 9e76d07cc2a071bd1cd7c480d9b4f8759adf1e2d Mon Sep 17 00:00:00 2001 From: dwindown Date: Wed, 31 Dec 2025 12:47:42 +0700 Subject: [PATCH] Add routeable lesson URLs for bootcamp pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement deep linking to individual lessons with URL pattern: - Route: /bootcamp/{product-slug}/{lessonId} - lessonId parameter is optional for backward compatibility - When no lessonId provided, defaults to first lesson - Clicking lessons updates URL without page reload - URL parameter drives lesson selection on page load Changes: - Update App.tsx route to accept optional :lessonId parameter - Add lessonId extraction in Bootcamp.tsx useParams - Implement handleSelectLesson to update URL on lesson click - Update lesson selection logic to read from URL parameter - Fallback to first lesson if lessonId not found 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- src/App.tsx | 2 +- src/pages/Bootcamp.tsx | 25 ++++++++++++++++++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 9101255..9a20ba2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -57,7 +57,7 @@ const App = () => ( } /> } /> } /> - } /> + } /> } /> } /> } /> diff --git a/src/pages/Bootcamp.tsx b/src/pages/Bootcamp.tsx index 75b8ab3..4833c63 100644 --- a/src/pages/Bootcamp.tsx +++ b/src/pages/Bootcamp.tsx @@ -55,7 +55,7 @@ interface UserReview { } export default function Bootcamp() { - const { slug } = useParams<{ slug: string }>(); + const { slug, lessonId } = useParams<{ slug: string; lessonId?: string }>(); const navigate = useNavigate(); const { user, loading: authLoading } = useAuth(); @@ -134,7 +134,20 @@ export default function Bootcamp() { })); setModules(sortedModules); - if (sortedModules.length > 0 && sortedModules[0].lessons.length > 0) { + // Select lesson based on URL parameter or default to first lesson + const allLessons = sortedModules.flatMap(m => m.lessons); + + if (lessonId) { + // Find the lesson by ID from URL + const lessonFromUrl = allLessons.find(l => l.id === lessonId); + if (lessonFromUrl) { + setSelectedLesson(lessonFromUrl); + } else if (allLessons.length > 0) { + // If lesson not found, default to first lesson + setSelectedLesson(allLessons[0]); + } + } else if (allLessons.length > 0 && sortedModules[0].lessons.length > 0) { + // No lessonId in URL, select first lesson setSelectedLesson(sortedModules[0].lessons[0]); } } @@ -170,6 +183,12 @@ export default function Bootcamp() { return progress.some(p => p.lesson_id === lessonId); }; + const handleSelectLesson = (lesson: Lesson) => { + setSelectedLesson(lesson); + // Update URL without full page reload + navigate(`/bootcamp/${slug}/${lesson.id}`); + }; + const markAsCompleted = async () => { if (!selectedLesson || !user || !product) return; @@ -347,7 +366,7 @@ export default function Bootcamp() { key={lesson.id} onClick={() => { if (isReleased) { - setSelectedLesson(lesson); + handleSelectLesson(lesson); setMobileMenuOpen(false); } }}