diff --git a/src/components/content/courses/CourseHeader.js b/src/components/content/courses/CourseHeader.js new file mode 100644 index 0000000..d05c362 --- /dev/null +++ b/src/components/content/courses/CourseHeader.js @@ -0,0 +1,113 @@ +import React from 'react'; +import { Tag } from 'primereact/tag'; +import { useRouter } from 'next/router'; +import GenericButton from '@/components/buttons/GenericButton'; +import Image from 'next/image'; +import 'primeicons/primeicons.css'; + +const CourseHeader = ({ + course, + isMobileView, + isCompleted, + navbarHeight, + isNavbarMode = false +}) => { + const router = useRouter(); + + // Handle back button navigation + const handleBackNavigation = () => { + const { active, slug } = router.query; + + // If we're on a specific lesson (has active param), remove it and stay on course page + if (active !== undefined) { + router.push(`/course/${slug}`, undefined, { shallow: true }); + } else { + // If we're on the main course page (no active param), go back to previous page + router.push('/'); + } + }; + + if (!course) return null; + + // Navbar mode - compact version for the top navbar + if (isNavbarMode) { + return ( +
router.push(`/course/${router.query.slug}`)} + > + { + e.stopPropagation(); + router.push('/'); + }} + className="mr-2 pl-0 p-button-rounded p-button-text text-gray-300 hover:text-white" + rounded={true} + text={true} + aria-label="Go back to home" + /> + + {course.image && ( +
+ {course.name} +
+ )} + +
+

+ {course.name} +

+ + {isCompleted && !isMobileView && ( + + )} +
+
+ ); + } + + // Standard mode - for course page content + return ( +
+
+ + {!isMobileView && course.image && ( +
+ {course.name} +
+ )} +

+ {course.name} +

+
+
+ {isCompleted && ( + + )} +
+
+ ); +}; + +export default CourseHeader; \ No newline at end of file diff --git a/src/components/content/courses/CourseSidebar.js b/src/components/content/courses/CourseSidebar.js index 4b16697..209856d 100644 --- a/src/components/content/courses/CourseSidebar.js +++ b/src/components/content/courses/CourseSidebar.js @@ -4,6 +4,7 @@ import { Button } from 'primereact/button'; import { Sidebar } from 'primereact/sidebar'; import Image from 'next/image'; import { useImageProxy } from '@/hooks/useImageProxy'; +import GenericButton from '@/components/buttons/GenericButton'; const CourseSidebar = ({ lessons, @@ -18,6 +19,7 @@ const CourseSidebar = ({ }) => { const { returnImageProxy } = useImageProxy(); const [visible, setVisible] = useState(true); + const navbarHeight = 60; // Match the navbar height // Sync with parent state if provided useEffect(() => { @@ -83,16 +85,17 @@ const CourseSidebar = ({ // Sidebar content component for reuse const SidebarContent = () => ( -
+

Course Lessons

{visible && !hideToggleButton && !isMobileView && ( -
@@ -121,9 +124,12 @@ const CourseSidebar = ({ transform: 'translateY(-50%)' }} > -
); @@ -176,7 +176,8 @@ const CourseSidebar = ({ visible ? 'w-80 opacity-100' : 'w-0 opacity-0 overflow-hidden' }`} > -
+
{/* Adjusted to match new header spacing */}
diff --git a/src/components/menutab/MenuTab.js b/src/components/menutab/MenuTab.js index 707af67..1e6f7dd 100644 --- a/src/components/menutab/MenuTab.js +++ b/src/components/menutab/MenuTab.js @@ -1,6 +1,7 @@ import React from 'react'; import { TabMenu } from 'primereact/tabmenu'; import { Button } from 'primereact/button'; +import GenericButton from '@/components/buttons/GenericButton'; export default function MenuTab({ items, activeIndex, onTabChange, sidebarVisible, onToggleSidebar, isMobileView = false }) { return ( @@ -25,12 +26,12 @@ export default function MenuTab({ items, activeIndex, onTabChange, sidebarVisibl {/* Sidebar toggle button positioned at the far right - hidden on mobile */} {!isMobileView && (
-
)} diff --git a/src/components/navbar/Navbar.js b/src/components/navbar/Navbar.js index 6e3d1b2..4dd973c 100644 --- a/src/components/navbar/Navbar.js +++ b/src/components/navbar/Navbar.js @@ -8,6 +8,10 @@ import { useSession } from 'next-auth/react'; import 'primereact/resources/primereact.min.css'; import 'primeicons/primeicons.css'; import useWindowWidth from '@/hooks/useWindowWidth'; +import CourseHeader from '../content/courses/CourseHeader'; +import { useNDKContext } from '@/context/NDKContext'; +import { nip19 } from 'nostr-tools'; +import { parseCourseEvent } from '@/utils/nostr'; const Navbar = () => { const router = useRouter(); @@ -17,6 +21,51 @@ const Navbar = () => { const [isHovered, setIsHovered] = useState(false); const [showMobileSearch, setShowMobileSearch] = useState(false); const menu = useRef(null); + const { ndk } = useNDKContext(); + const [course, setCourse] = useState(null); + const [isCompleted, setIsCompleted] = useState(false); + + // Check if we're on a course page + const isCoursePage = router.pathname.startsWith('/course/'); + + // Fetch course data when on a course page + useEffect(() => { + if (isCoursePage && router.isReady && ndk) { + const fetchCourse = async () => { + try { + const { slug } = router.query; + let identifier; + + if (slug.includes('naddr')) { + const { data } = nip19.decode(slug); + identifier = data?.identifier; + } else { + identifier = slug; + } + + if (identifier) { + const event = await ndk.fetchEvent({ '#d': [identifier] }); + if (event) { + const parsedCourse = parseCourseEvent(event); + setCourse(parsedCourse); + + // Check if course is completed (simplified for nav display) + if (session?.user?.completedCourses?.includes(identifier)) { + setIsCompleted(true); + } + } + } + } catch (error) { + console.error('Error fetching course for navbar:', error); + } + }; + + fetchCourse(); + } else { + setCourse(null); + setIsCompleted(false); + } + }, [isCoursePage, router.isReady, router.query, ndk, session?.user?.completedCourses]); // Lock/unlock body scroll when mobile search is shown/hidden useEffect(() => { @@ -65,21 +114,33 @@ const Navbar = () => { > {/* Left section */}
-
router.push('/')} - className="flex flex-row items-center justify-center cursor-pointer hover:opacity-80" - > - logo -

- PlebDevs -

-
+ ) : ( + /* Regular PlebDevs branding */ +
router.push('/')} + className="flex flex-row items-center justify-center cursor-pointer hover:opacity-80" + > + logo +

+ PlebDevs +

+
+ )} + {windowWidth > 600 ? (
{ const [nAddress, setNAddress] = useState(null); const windowWidth = useWindowWidth(); const isMobileView = windowWidth <= 968; - const [activeTab, setActiveTab] = useState('content'); // Default to content tab on mobile + const [activeTab, setActiveTab] = useState('overview'); // Default to overview tab + const navbarHeight = 60; // Match the height from Navbar component useEffect(() => { if (router.isReady) { @@ -200,6 +202,24 @@ const Course = () => { } }, [router.isReady, router.query.slug]); + useEffect(() => { + if (router.isReady) { + const { active } = router.query; + if (active !== undefined) { + setActiveIndex(parseInt(active, 10)); + // If we have an active lesson, switch to content tab + setActiveTab('content'); + } else { + setActiveIndex(0); + // Default to overview tab when no active parameter + setActiveTab('overview'); + } + + // Auto-open sidebar on desktop, close on mobile + setSidebarVisible(!isMobileView); + } + }, [router.isReady, router.query, isMobileView]); + const setCompleted = useCallback(lessonId => { setCompletedLessons(prev => [...prev, lessonId]); }, []); @@ -220,12 +240,14 @@ const Course = () => { paidCourse, loading: courseLoading, } = useCourseData(ndk, fetchAuthor, router); + const { lessons, uniqueLessons, setLessons } = useLessons( ndk, fetchAuthor, lessonIds, course?.pubkey ); + const { decryptionPerformed, loading: decryptionLoading } = useDecryption( session, paidCourse, @@ -234,24 +256,13 @@ const Course = () => { setLessons ); - useEffect(() => { - if (router.isReady) { - const { active } = router.query; - if (active !== undefined) { - setActiveIndex(parseInt(active, 10)); - } else { - setActiveIndex(0); - } - - // Auto-open sidebar on desktop, close on mobile - setSidebarVisible(!isMobileView); - - // Reset to content tab when switching to mobile - if (isMobileView) { - setActiveTab('content'); - } - } - }, [router.isReady, router.query, isMobileView]); + // Check if course is completed - moved after course is initialized + const isCourseCompleted = useMemo(() => { + if (!course || !completedLessons.length) return false; + // A course is completed if at least one lesson is completed + // You can change this logic if needed (e.g., all lessons must be completed) + return completedLessons.length > 0; + }, [completedLessons, course]); useEffect(() => { if (uniqueLessons.length > 0) { @@ -367,7 +378,7 @@ const Course = () => { } items.push({ - label: 'Q&A', + label: 'Comments', icon: 'pi pi-comments', }); @@ -387,7 +398,6 @@ const Course = () => {
); }; - // Render Course Overview section const renderOverviewSection = () => { // Get isCompleted status for use in the component @@ -474,20 +484,13 @@ const Course = () => { return ( <> - {/* {course && paidCourse !== null && ( - - )} */} - -
+
{/* Tab navigation using MenuTab component */} -
+