feat: add landing pages and section navigation
- Add home landing page with section cards (CSS, HTML, Tailwind) - Add section landing pages with module grid and progress tracking - Implement extended URL routing for pages, sections, and lessons - Create sections.js configuration for module categorization - Exclude welcome/goodbye modules from progress stats - Add main navigation links in header (desktop only) - Update logo click to navigate to home landing Routes: - # → Home landing - #css, #html, #tailwind → Section landing pages - #module/index → Lesson (unchanged) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
@@ -1,26 +1,81 @@
|
||||
/**
|
||||
* URL Router for Code Crispies
|
||||
* Handles hash-based routing for shareable lesson links
|
||||
* Format: #module-id/lesson-index (e.g., #flexbox/2)
|
||||
* Handles hash-based routing for pages, sections, and lessons
|
||||
*
|
||||
* Route formats:
|
||||
* - # -> Home landing page
|
||||
* - #css -> CSS section landing
|
||||
* - #html -> HTML section landing
|
||||
* - #tailwind -> Tailwind section landing
|
||||
* - #reference/css -> CSS cheatsheet
|
||||
* - #module/index -> Lesson (e.g., #flexbox/2)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Parse current URL hash into module and lesson info
|
||||
* @returns {{ moduleId: string, lessonIndex: number } | null}
|
||||
* Route types
|
||||
*/
|
||||
export const RouteType = {
|
||||
HOME: "home",
|
||||
SECTION: "section",
|
||||
REFERENCE: "reference",
|
||||
LESSON: "lesson"
|
||||
};
|
||||
|
||||
/**
|
||||
* Valid section IDs
|
||||
*/
|
||||
const SECTIONS = ["css", "html", "tailwind"];
|
||||
|
||||
/**
|
||||
* Parse current URL hash into route info
|
||||
* @returns {{ type: string, moduleId?: string, lessonIndex?: number, sectionId?: string, refId?: string } | null}
|
||||
*/
|
||||
export function parseHash() {
|
||||
const hash = window.location.hash.slice(1); // Remove '#'
|
||||
if (!hash) return null;
|
||||
|
||||
// Empty hash = home
|
||||
if (!hash) {
|
||||
return { type: RouteType.HOME };
|
||||
}
|
||||
|
||||
const parts = hash.split("/");
|
||||
if (parts.length !== 2) return null;
|
||||
|
||||
const moduleId = parts[0];
|
||||
const lessonIndex = parseInt(parts[1], 10);
|
||||
// Single segment routes
|
||||
if (parts.length === 1) {
|
||||
const segment = parts[0];
|
||||
|
||||
if (!moduleId || isNaN(lessonIndex) || lessonIndex < 0) return null;
|
||||
// Section landing pages
|
||||
if (SECTIONS.includes(segment)) {
|
||||
return { type: RouteType.SECTION, sectionId: segment };
|
||||
}
|
||||
|
||||
return { moduleId, lessonIndex };
|
||||
// Reference index (no specific ref)
|
||||
if (segment === "reference") {
|
||||
return { type: RouteType.REFERENCE, refId: null };
|
||||
}
|
||||
|
||||
// Single segment could be module with implicit lesson 0
|
||||
return { type: RouteType.LESSON, moduleId: segment, lessonIndex: 0 };
|
||||
}
|
||||
|
||||
// Two segment routes
|
||||
if (parts.length === 2) {
|
||||
// Reference subpages
|
||||
if (parts[0] === "reference") {
|
||||
return { type: RouteType.REFERENCE, refId: parts[1] };
|
||||
}
|
||||
|
||||
// Lesson route (existing behavior)
|
||||
const moduleId = parts[0];
|
||||
const lessonIndex = parseInt(parts[1], 10);
|
||||
|
||||
if (moduleId && !isNaN(lessonIndex) && lessonIndex >= 0) {
|
||||
return { type: RouteType.LESSON, moduleId, lessonIndex };
|
||||
}
|
||||
}
|
||||
|
||||
// Invalid route
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,6 +90,17 @@ export function updateHash(moduleId, lessonIndex) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update URL to a specific route
|
||||
* @param {string} route - The route string (e.g., "css", "reference/flexbox", "")
|
||||
*/
|
||||
export function navigateTo(route) {
|
||||
const newHash = route ? `#${route}` : "#";
|
||||
if (window.location.hash !== newHash) {
|
||||
history.pushState(null, "", newHash);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace URL hash without history entry (for invalid URL fallbacks)
|
||||
* @param {string} moduleId
|
||||
@@ -45,6 +111,15 @@ export function replaceHash(moduleId, lessonIndex) {
|
||||
history.replaceState(null, "", newHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace URL to a specific route without history entry
|
||||
* @param {string} route - The route string
|
||||
*/
|
||||
export function replaceTo(route) {
|
||||
const newHash = route ? `#${route}` : "#";
|
||||
history.replaceState(null, "", newHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build full shareable URL for current lesson
|
||||
* @param {string} moduleId
|
||||
@@ -55,3 +130,11 @@ export function getShareableUrl(moduleId, lessonIndex) {
|
||||
const base = window.location.origin + window.location.pathname;
|
||||
return `${base}#${moduleId}/${lessonIndex}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get valid section IDs
|
||||
* @returns {string[]}
|
||||
*/
|
||||
export function getSectionIds() {
|
||||
return [...SECTIONS];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user