Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions scripts/add-hub-certificates.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const COURSE_NAMES = {
"php-fundamentals": "PHP Fundamentals",
"ruby-fundamentals": "Ruby Fundamentals",
"ruby-gems": "Ruby Gems",
"ruby-file-handling": "Ruby File Handling",
"csharp-fundamentals": "C# Fundamentals",
"sql-fundamentals": "SQL Fundamentals",
"sql-queries": "SQL Queries",
Expand Down
234 changes: 224 additions & 10 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
import {
courseStackGroups,
inferLanguageFromLearnPath,
} from "./features/language/courseCatalog";
import React, { Suspense, lazy } from "react";
import {
BrowserRouter as Router,
Expand Down Expand Up @@ -290,6 +286,26 @@ const CsharpHub = lazyWithChunkRetry(
const CsharpLessonPage = lazyWithChunkRetry(
() => import("./features/learn/csharp-fundamentals/pages/CsharpLessonPage"),
);
const RubyFileHandlingHub = lazyWithChunkRetry(
() => import("./features/learn/ruby-file-handling/pages/RubyFileHandlingHub"),
);
const RubyFileHandlingLessonPage = lazyWithChunkRetry(
() =>
import("./features/learn/ruby-file-handling/pages/RubyFileHandlingLessonPage"),
);
const RubyOopHub = lazyWithChunkRetry(
() => import("./features/learn/ruby-oop/pages/rubyOopHub"),
);
const RubyOopLessonPage = lazyWithChunkRetry(
() => import("./features/learn/ruby-oop/pages/rubyOopLessonPage"),
);
const RubyBlocksModulesHub = lazyWithChunkRetry(
() => import("./features/learn/ruby-blocks-modules/pages/rubyBlocksModulesHub"),
);
const RubyBlocksModulesLessonPage = lazyWithChunkRetry(
() =>
import("./features/learn/ruby-blocks-modules/pages/rubyBlocksModulesLessonPage"),
);

const PageFallback = () => (
<div className="loading">
Expand Down Expand Up @@ -378,10 +394,17 @@ function MainApp({
theme,
onThemeChange,
}) {
const location = useLocation();
const [isSidebarOpen, setIsSidebarOpen] = React.useState(false);
const toggleSidebar = () => setIsSidebarOpen((o) => !o);
const closeSidebar = () => setIsSidebarOpen(false);

React.useEffect(() => {
if (location.pathname.startsWith("/learn/")) {
setIsSidebarOpen(false);
}
}, [location.pathname]);

React.useEffect(() => {
const mq = window.matchMedia("(max-width: 900px)");
const lock = isSidebarOpen && mq.matches;
Expand Down Expand Up @@ -450,6 +473,12 @@ function MainApp({
path="/daily-challenge"
element={<DailyChallenge theme={theme} />}
/>
{/*
NOTE: The Ruby File Handling routes used to live here, which was the bug —
this MainApp shell always renders the docs Navbar + Sidebar ("CURRENT STACK",
"NAVIGATE", "FILES"). They've been moved to AppRoutes below, wrapped in
LearnShell only (matching every other course), so they render full-width.
*/}
<Route path="*" element={<Navigate to="/hub" replace />} />
</Routes>
</Suspense>
Expand Down Expand Up @@ -651,10 +680,51 @@ function AppRoutes() {
}, []);

React.useEffect(() => {
const inferred = inferLanguageFromLearnPath(location.pathname);
if (!inferred) return;
const stack = courseStackGroups.find((entry) => entry.id === inferred);
handleLanguageSelect(stack?.label || inferred, { stay: true });
const path = location.pathname;
if (
path.startsWith("/learn/python-fundamentals") ||
path.startsWith("/learn/numpy-py") ||
path.startsWith("/learn/pandas-py") ||
path.startsWith("/learn/matplotlib-py") ||
path.startsWith("/learn/fastapi-py") ||
path.startsWith("/learn/matplotlib-py") ||
path.startsWith("/learn/ai_ml-py")
) {
handleLanguageSelect("Python", { stay: true });
} else if (path.startsWith("/learn/js-fundamentals")) {
handleLanguageSelect("JavaScript", { stay: true });
} else if (path.startsWith("/learn/c-sharp-fundamentals")) {
handleLanguageSelect("C#", { stay: true });
} else if (
path.startsWith("/learn/java-fundamentals") ||
path.startsWith("/learn/java-intermediate") ||
path.startsWith("/learn/java-exception") ||
path.startsWith("/learn/java-multithreading") ||
path.startsWith("/learn/java-jdbc") ||
path.startsWith("/learn/java-spring-boot") ||
path.startsWith("/learn/java-projects")
) {
handleLanguageSelect("Java", { stay: true });
} else if (
path.startsWith("/learn/php-fundamentals") ||
path.startsWith("/learn/php-forms") ||
path.startsWith("/learn/php-sessions") ||
path.startsWith("/learn/php-mysql") ||
path.startsWith("/learn/php-oop")
) {
handleLanguageSelect("PHP", { stay: true });
} else if (
path.startsWith("/learn/oops-cpp") ||
path.startsWith("/learn/pointers-cpp")
) {
handleLanguageSelect("C++", { stay: true });
} else if (
path.startsWith("/learn/ruby-fundamentals") ||
path.startsWith("/learn/ruby-gems") ||
path.startsWith("/learn/ruby-file-handling")
) {
handleLanguageSelect("Ruby", { stay: true });
}
}, [location.pathname, handleLanguageSelect]);

React.useEffect(() => {
Expand Down Expand Up @@ -1726,7 +1796,150 @@ function AppRoutes() {
</ThemedShell>
}
/>
<Route path="/profile" element={<ProfileRedirect />} /><Route

{/* ── Ruby File Handling ── (moved here from MainApp — was rendering the docs
Navbar/Sidebar shell instead of LearnShell, which caused the stray
"CURRENT STACK / NAVIGATE / FILES" sidebar and the leftover C# stack label) */}
<Route
path="/learn/ruby-file-handling"
element={
<ThemedShell theme={theme}>
<LearnShell
theme={theme}
onThemeChange={handleThemeChange}
onGoToStackPicker={goToStackPicker}
selectedLanguage={selectedLanguage}
>
<RubyFileHandlingHub />
</LearnShell>
</ThemedShell>
}
/>
<Route
path="/learn/ruby-file-handling/lesson/:lessonId"
element={
<ThemedShell theme={theme}>
<LearnShell
theme={theme}
onThemeChange={handleThemeChange}
onGoToStackPicker={goToStackPicker}
selectedLanguage={selectedLanguage}
>
<RubyFileHandlingLessonPage />
</LearnShell>
</ThemedShell>
}
/>
<Route
path="/learn/ruby-file-handling/:lessonId"
element={
<ThemedShell theme={theme}>
<LearnShell
theme={theme}
onThemeChange={handleThemeChange}
onGoToStackPicker={goToStackPicker}
selectedLanguage={selectedLanguage}
>
<RubyFileHandlingLessonPage />
</LearnShell>
</ThemedShell>
}
/>

<Route
path="/learn/ruby-oop"
element={
<ThemedShell theme={theme}>
<LearnShell
theme={theme}
onThemeChange={handleThemeChange}
onGoToStackPicker={goToStackPicker}
selectedLanguage={selectedLanguage}
>
<RubyOopHub />
</LearnShell>
</ThemedShell>
}
/>
<Route
path="/learn/ruby-oop/lesson/:lessonId"
element={
<ThemedShell theme={theme}>
<LearnShell
theme={theme}
onThemeChange={handleThemeChange}
onGoToStackPicker={goToStackPicker}
selectedLanguage={selectedLanguage}
>
<RubyOopLessonPage />
</LearnShell>
</ThemedShell>
}
/>
<Route
path="/learn/ruby-oop/:lessonId"
element={
<ThemedShell theme={theme}>
<LearnShell
theme={theme}
onThemeChange={handleThemeChange}
onGoToStackPicker={goToStackPicker}
selectedLanguage={selectedLanguage}
>
<RubyOopLessonPage />
</LearnShell>
</ThemedShell>
}
/>

<Route
path="/learn/ruby-blocks-modules"
element={
<ThemedShell theme={theme}>
<LearnShell
theme={theme}
onThemeChange={handleThemeChange}
onGoToStackPicker={goToStackPicker}
selectedLanguage={selectedLanguage}
>
<RubyBlocksModulesHub />
</LearnShell>
</ThemedShell>
}
/>
<Route
path="/learn/ruby-blocks-modules/lesson/:lessonId"
element={
<ThemedShell theme={theme}>
<LearnShell
theme={theme}
onThemeChange={handleThemeChange}
onGoToStackPicker={goToStackPicker}
selectedLanguage={selectedLanguage}
>
<RubyBlocksModulesLessonPage />
</LearnShell>
</ThemedShell>
}
/>
<Route
path="/learn/ruby-blocks-modules/:lessonId"
element={
<ThemedShell theme={theme}>
<LearnShell
theme={theme}
onThemeChange={handleThemeChange}
onGoToStackPicker={goToStackPicker}
selectedLanguage={selectedLanguage}
>
<RubyBlocksModulesLessonPage />
</LearnShell>
</ThemedShell>
}
/>

<Route path="/profile" element={<ProfileRedirect />} />
<Route
path="/learn/sql-fundamentals/*"
element={
<ThemedShell theme={theme}>
Expand Down Expand Up @@ -2266,6 +2479,7 @@ function AppRoutes() {
</ThemedShell>
}
/>
<Route path="/profile" element={<ProfileRedirect />} />
<Route
path="/*"
element={
Expand Down Expand Up @@ -2357,4 +2571,4 @@ function App() {
);
}

export default App;
export default App;
27 changes: 25 additions & 2 deletions src/features/language/courseCatalog.js
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ export const languageCourses = {
href: "/learn/php-oop",
accent: "#f59e0b",
},
{
{
title: "Laravel Basics",
tag: "Interactive Course",
icon: Layers3,
Expand Down Expand Up @@ -510,6 +510,12 @@ export const languageCourses = {
accent: "#9333ea",
},
{
title: "Ruby File Handling",
tag: "Interactive Course",
icon: FolderOpen,
description:
"Read, write, and manage files in Ruby — from simple text files to CSV, JSON, YAML, and binary data with robust error handling.",
href: "/learn/ruby-file-handling",
title: "Ruby Blocks & Modules",
tag: "Interactive Course",
icon: Layers3,
Expand All @@ -523,6 +529,18 @@ export const languageCourses = {
tag: "Interactive Course",
icon: Boxes,
description:
"Master object-oriented programming in Ruby — classes, inheritance, modules, mixins, encapsulation, polymorphism, and metaprogramming.",
href: "/learn/ruby-oop",
accent: "#9333ea",
},
{
title: "Ruby Blocks & Modules",
tag: "Interactive Course",
icon: Layers3,
description:
"Deep dive into Ruby blocks, procs, lambdas, modules, and mixins — the building blocks of Ruby's expressive and modular design.",
href: "/learn/ruby-blocks-modules",
accent: "#0891b2",
"Classes, instances, and initialize; encapsulation with attr_reader/writer/accessor; inheritance, modules, and polymorphism in Ruby.",
href: "/learn/ruby-oop",
accent: "#f59e0b",
Expand Down Expand Up @@ -731,6 +749,9 @@ export const learnNavByLanguage = {
ruby: [
{ label: "Ruby Basics", to: "/learn/ruby-fundamentals" },
{ label: "Ruby Gems", to: "/learn/ruby-gems" },
{ label: "File Handling", to: "/learn/ruby-file-handling" },
{ label: "Ruby OOP", to: "/learn/ruby-oop" },
{ label: "Blocks & Modules", to: "/learn/ruby-blocks-modules" },
{ label: "Blocks & Modules", to: "/learn/ruby-blocks-modules" },
{ label: "Ruby OOP", to: "/learn/ruby-oop" },
],
Expand Down Expand Up @@ -818,7 +839,6 @@ export function inferLanguageFromLearnPath(pathname = "") {
pathname.startsWith("/learn/php-forms") ||
pathname.startsWith("/learn/php-sessions") ||
pathname.startsWith("/learn/php-mysql") ||
pathname.startsWith("/learn/php-oop") ||
pathname.startsWith("/learn/laravel-basics") ||
pathname.startsWith("/learn/php-projects")
) {
Expand All @@ -827,6 +847,9 @@ export function inferLanguageFromLearnPath(pathname = "") {
if (
pathname.startsWith("/learn/ruby-fundamentals") ||
pathname.startsWith("/learn/ruby-gems") ||
pathname.startsWith("/learn/ruby-file-handling") ||
pathname.startsWith("/learn/ruby-oop") ||
pathname.startsWith("/learn/ruby-blocks-modules")
pathname.startsWith("/learn/ruby-blocks-modules") ||
pathname.startsWith("/learn/ruby-oop")
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ export default function RubyBlocksModulesHub() {
</div>
</div>
</div>

<div className="oops-stage-tabs" style={{ padding: "0 1.5rem", marginTop: "0.5rem" }}>
{[ ["beginner","Beginner"], ["intermediate","Intermediate"], ["advanced","Advanced"], ["pro","Pro"] ].map(([id,label])=> (
<button key={id} type="button" className={stage===id?"active stage-tab":"stage-tab"} onClick={()=>setStage(id)} style={{ marginRight: 8 }}>
Expand Down
Loading