mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-06-23 16:05:24 +00:00
Major updates on course and lesson layout with tabs
This commit is contained in:
parent
a3e8cda6f4
commit
874d903020
@ -21,6 +21,7 @@ import WelcomeModal from '@/components/onboarding/WelcomeModal';
|
|||||||
import { ProgressSpinner } from 'primereact/progressspinner';
|
import { ProgressSpinner } from 'primereact/progressspinner';
|
||||||
import { Toast } from 'primereact/toast';
|
import { Toast } from 'primereact/toast';
|
||||||
import MoreOptionsMenu from '@/components/ui/MoreOptionsMenu';
|
import MoreOptionsMenu from '@/components/ui/MoreOptionsMenu';
|
||||||
|
import { Divider } from 'primereact/divider';
|
||||||
|
|
||||||
export default function CourseDetails({
|
export default function CourseDetails({
|
||||||
processedEvent,
|
processedEvent,
|
||||||
@ -201,54 +202,63 @@ export default function CourseDetails({
|
|||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<Toast ref={toastRef} />
|
<Toast ref={toastRef} />
|
||||||
<WelcomeModal />
|
<WelcomeModal />
|
||||||
<div className="relative w-full h-[400px] mb-8">
|
|
||||||
<Image
|
<div className="flex flex-col">
|
||||||
alt="course image"
|
{/* Header with course image, title and options */}
|
||||||
src={returnImageProxy(processedEvent.image)}
|
<div className="flex mb-6">
|
||||||
fill
|
{/* Course image */}
|
||||||
className="object-cover rounded-b-lg"
|
<div className="relative w-52 h-32 mr-6 flex-shrink-0 rounded-lg overflow-hidden">
|
||||||
/>
|
<Image
|
||||||
<div className="absolute inset-0 bg-black bg-opacity-20"></div>
|
alt="course image"
|
||||||
</div>
|
src={returnImageProxy(processedEvent.image)}
|
||||||
<div className="w-full mx-auto px-4 py-8 -mt-32 relative z-10 max-mob:px-0 max-tab:px-0">
|
fill
|
||||||
<i
|
className="object-cover"
|
||||||
className={`pi pi-arrow-left cursor-pointer hover:opacity-75 absolute top-0 left-4`}
|
|
||||||
onClick={() => router.push('/')}
|
|
||||||
/>
|
|
||||||
<div className="mb-8 bg-gray-800/70 rounded-lg p-4 max-mob:rounded-t-none max-tab:rounded-t-none">
|
|
||||||
{isCompleted && <Tag severity="success" value="Completed" />}
|
|
||||||
<div className="flex flex-row items-center justify-between w-full">
|
|
||||||
<h1 className="text-4xl font-bold text-white">{processedEvent.name}</h1>
|
|
||||||
<ZapDisplay
|
|
||||||
zapAmount={zapAmount}
|
|
||||||
event={processedEvent}
|
|
||||||
zapsLoading={zapsLoading && zapAmount === 0}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-2 mt-2 mb-4">
|
|
||||||
{processedEvent.topics &&
|
{/* Title and options */}
|
||||||
processedEvent.topics.length > 0 &&
|
<div className="flex-1">
|
||||||
processedEvent.topics.map((topic, index) => (
|
<div className="flex items-start justify-between mb-2">
|
||||||
<Tag className="text-white" key={index} value={topic}></Tag>
|
<div>
|
||||||
))}
|
{isCompleted && (
|
||||||
</div>
|
<Tag severity="success" value="Completed" className="mb-2" />
|
||||||
<div className="text-xl text-gray-200 mb-4 mt-4 max-mob:text-base">
|
)}
|
||||||
{processedEvent.description &&
|
<h1 className="text-2xl font-bold text-white">{processedEvent.name}</h1>
|
||||||
processedEvent.description
|
</div>
|
||||||
.split('\n')
|
<div className="flex items-center space-x-2">
|
||||||
.map((line, index) => <p key={index}>{line}</p>)}
|
<ZapDisplay
|
||||||
</div>
|
zapAmount={zapAmount}
|
||||||
<div className="flex items-center justify-between mt-8">
|
event={processedEvent}
|
||||||
|
zapsLoading={zapsLoading && zapAmount === 0}
|
||||||
|
/>
|
||||||
|
<MoreOptionsMenu
|
||||||
|
menuItems={menuItems}
|
||||||
|
additionalLinks={processedEvent?.additionalLinks || []}
|
||||||
|
isMobileView={isMobileView}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Topics/tags */}
|
||||||
|
<div className="flex flex-wrap gap-2 mb-3">
|
||||||
|
{processedEvent.topics &&
|
||||||
|
processedEvent.topics.length > 0 &&
|
||||||
|
processedEvent.topics.map((topic, index) => (
|
||||||
|
<Tag className="text-white" key={index} value={topic}></Tag>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Author info */}
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<Image
|
<Image
|
||||||
alt="avatar image"
|
alt="avatar image"
|
||||||
src={returnImageProxy(author?.avatar, author?.pubkey)}
|
src={returnImageProxy(author?.avatar, author?.pubkey)}
|
||||||
width={50}
|
width={32}
|
||||||
height={50}
|
height={32}
|
||||||
className="rounded-full mr-4"
|
className="rounded-full mr-2"
|
||||||
/>
|
/>
|
||||||
<p className="text-lg text-white">
|
<p className="text-gray-300">
|
||||||
By{' '}
|
Created by{' '}
|
||||||
<a
|
<a
|
||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@ -258,15 +268,63 @@ export default function CourseDetails({
|
|||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end">
|
</div>
|
||||||
<MoreOptionsMenu
|
</div>
|
||||||
menuItems={menuItems}
|
|
||||||
additionalLinks={processedEvent?.additionalLinks || []}
|
<Divider className="my-4" />
|
||||||
isMobileView={isMobileView}
|
|
||||||
/>
|
{/* Course details */}
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
|
{/* Left column: Description */}
|
||||||
|
<div className="lg:col-span-2">
|
||||||
|
<h2 className="text-xl font-semibold mb-3 text-white">About This Course</h2>
|
||||||
|
<div className="text-gray-300 mb-4">
|
||||||
|
{processedEvent.description &&
|
||||||
|
processedEvent.description
|
||||||
|
.split('\n')
|
||||||
|
.map((line, index) => <p key={index} className="mb-2">{line}</p>)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Payment section */}
|
||||||
|
<div className="mt-4">
|
||||||
|
{renderPaymentMessage()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right column: Course details */}
|
||||||
|
<div className="bg-gray-800 rounded-lg p-4">
|
||||||
|
<h2 className="text-xl font-semibold mb-3 text-white">Course Information</h2>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-gray-300 font-medium mb-2">Course Content</h3>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-gray-400">Lessons</p>
|
||||||
|
<p className="font-semibold text-white">{lessons.length}</p>
|
||||||
|
</div>
|
||||||
|
{paidCourse && (
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-gray-400">Price</p>
|
||||||
|
<p className="font-semibold text-white">{processedEvent.price} sats</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{processedEvent.published && (
|
||||||
|
<div>
|
||||||
|
<h3 className="text-gray-300 font-medium mb-2">Details</h3>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-gray-400">Published</p>
|
||||||
|
<p className="font-semibold text-white">
|
||||||
|
{new Date(processedEvent.published * 1000).toLocaleDateString()}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full mt-4">{renderPaymentMessage()}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import React from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Tag } from 'primereact/tag';
|
import { Tag } from 'primereact/tag';
|
||||||
|
import { Button } from 'primereact/button';
|
||||||
|
import { Sidebar } from 'primereact/sidebar';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { useImageProxy } from '@/hooks/useImageProxy';
|
import { useImageProxy } from '@/hooks/useImageProxy';
|
||||||
|
|
||||||
@ -10,9 +12,26 @@ const CourseSidebar = ({
|
|||||||
completedLessons,
|
completedLessons,
|
||||||
isMobileView,
|
isMobileView,
|
||||||
onClose,
|
onClose,
|
||||||
sidebarVisible,
|
sidebarVisible: parentSidebarVisible,
|
||||||
|
setSidebarVisible,
|
||||||
}) => {
|
}) => {
|
||||||
const { returnImageProxy } = useImageProxy();
|
const { returnImageProxy } = useImageProxy();
|
||||||
|
const [visible, setVisible] = useState(true);
|
||||||
|
|
||||||
|
// Sync with parent state if provided
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof parentSidebarVisible !== 'undefined') {
|
||||||
|
setVisible(parentSidebarVisible);
|
||||||
|
}
|
||||||
|
}, [parentSidebarVisible]);
|
||||||
|
|
||||||
|
const handleToggle = () => {
|
||||||
|
const newState = !visible;
|
||||||
|
setVisible(newState);
|
||||||
|
if (setSidebarVisible) {
|
||||||
|
setSidebarVisible(newState);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const LessonItem = ({ lesson, index }) => (
|
const LessonItem = ({ lesson, index }) => (
|
||||||
<li
|
<li
|
||||||
@ -61,51 +80,116 @@ const CourseSidebar = ({
|
|||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
|
|
||||||
// Desktop sidebar implementation
|
// Sidebar content component for reuse
|
||||||
|
const SidebarContent = () => (
|
||||||
|
<div className="flex flex-col h-full bg-gray-800 text-[#f8f8ff] px-4 py-4">
|
||||||
|
<div className="flex items-center justify-between border-b border-gray-700 pb-4 mb-4">
|
||||||
|
<h2 className="font-bold text-white text-lg">Course Lessons</h2>
|
||||||
|
{visible && (
|
||||||
|
<Button
|
||||||
|
icon="pi pi-times"
|
||||||
|
onClick={handleToggle}
|
||||||
|
className="p-button-rounded p-button-text text-gray-300 hover:text-white p-button-sm"
|
||||||
|
tooltip="Close sidebar"
|
||||||
|
tooltipOptions={{ position: 'left' }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="overflow-y-auto flex-1 pr-2">
|
||||||
|
<ul className="space-y-2">
|
||||||
|
{lessons.map((lesson, index) => (
|
||||||
|
<LessonItem key={index} lesson={lesson} index={index} />
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Toggle button (used for both desktop and mobile)
|
||||||
|
const ToggleButton = () => (
|
||||||
|
<div className="fixed right-0 top-1/3 z-50 m-0 p-0">
|
||||||
|
<Button
|
||||||
|
icon="pi pi-chevron-left"
|
||||||
|
onClick={handleToggle}
|
||||||
|
className="shadow-md border-0 rounded-r-none rounded-l-md bg-blue-600 hover:bg-blue-700"
|
||||||
|
tooltip="Show lessons"
|
||||||
|
tooltipOptions={{ position: 'left' }}
|
||||||
|
style={{ borderTopRightRadius: 0, borderBottomRightRadius: 0 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Desktop implementation with integrated toggle button
|
||||||
if (!isMobileView) {
|
if (!isMobileView) {
|
||||||
return (
|
return (
|
||||||
<div className="w-80 h-[calc(100vh-400px)] sticky top-8 overflow-hidden rounded-lg border border-gray-800 shadow-sm bg-gray-900">
|
<>
|
||||||
<div className="h-full overflow-y-auto">
|
{/* Sidebar content */}
|
||||||
<div className="flex flex-col p-4 h-full bg-gray-800 text-[#f8f8ff]">
|
<div className="relative flex flex-row-reverse">
|
||||||
<div className="flex items-center justify-between border-b border-gray-700 pb-4 mb-4">
|
<div
|
||||||
<h2 className="font-bold text-white text-lg">Course Lessons</h2>
|
className={`transition-all duration-300 flex ${
|
||||||
</div>
|
visible ? 'w-80' : 'w-0 overflow-hidden'
|
||||||
<div className="overflow-y-auto flex-1">
|
}`}
|
||||||
<ul className="space-y-2">
|
>
|
||||||
{lessons.map((lesson, index) => (
|
<div className="w-80 h-[calc(100vh-400px)] sticky top-8 overflow-hidden rounded-lg border border-gray-800 shadow-md bg-gray-900">
|
||||||
<LessonItem key={index} lesson={lesson} index={index} />
|
<div className="h-full overflow-y-auto">
|
||||||
))}
|
<SidebarContent />
|
||||||
</ul>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
{/* Detached toggle button when sidebar is closed */}
|
||||||
|
{!visible && <ToggleButton />}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mobile sidebar implementation - completely restructured for better scrolling
|
// Mobile implementation with PrimeReact's Sidebar
|
||||||
if (isMobileView && sidebarVisible) {
|
return (
|
||||||
return (
|
<>
|
||||||
<div className="w-full bg-gray-900 rounded-lg border border-gray-800 shadow-md overflow-hidden mb-4">
|
{/* Mobile toggle button - only shown when sidebar is closed */}
|
||||||
<div className="bg-gray-800 p-4 border-b border-gray-700">
|
{!visible && (
|
||||||
|
<div className="fixed right-0 top-20 z-40 m-0 p-0">
|
||||||
|
<Button
|
||||||
|
icon="pi pi-list"
|
||||||
|
onClick={handleToggle}
|
||||||
|
className="shadow-md bg-blue-600 hover:bg-blue-700 border-0 rounded-r-none rounded-l-md"
|
||||||
|
tooltip="Show lessons"
|
||||||
|
tooltipOptions={{ position: 'left' }}
|
||||||
|
style={{ borderTopRightRadius: 0, borderBottomRightRadius: 0 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Mobile sidebar */}
|
||||||
|
<Sidebar
|
||||||
|
visible={visible}
|
||||||
|
position="right"
|
||||||
|
onHide={handleToggle}
|
||||||
|
className="bg-gray-900 p-0 shadow-lg"
|
||||||
|
style={{ width: '85vw', maxWidth: '350px' }}
|
||||||
|
showCloseIcon={false}
|
||||||
|
modal={false}
|
||||||
|
>
|
||||||
|
<div className="bg-gray-800 p-5 border-b border-gray-700 flex justify-between items-center">
|
||||||
<h2 className="font-bold text-white text-xl">Course Lessons</h2>
|
<h2 className="font-bold text-white text-xl">Course Lessons</h2>
|
||||||
|
<Button
|
||||||
|
icon="pi pi-times"
|
||||||
|
onClick={handleToggle}
|
||||||
|
className="p-button-rounded p-button-text text-gray-300 hover:text-white"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Scrollable container with fixed height */}
|
<div className="overflow-y-auto h-full p-4 bg-gray-900">
|
||||||
<div className="overflow-y-scroll" style={{ maxHeight: '60vh', WebkitOverflowScrolling: 'touch' }}>
|
<ul className="space-y-3">
|
||||||
<div className="p-4 bg-gray-900">
|
{lessons.map((lesson, index) => (
|
||||||
<ul>
|
<LessonItem key={index} lesson={lesson} index={index} />
|
||||||
{lessons.map((lesson, index) => (
|
))}
|
||||||
<LessonItem key={index} lesson={lesson} index={index} />
|
</ul>
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Sidebar>
|
||||||
);
|
</>
|
||||||
}
|
);
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CourseSidebar;
|
export default CourseSidebar;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { DataTable } from 'primereact/datatable';
|
import { DataTable } from 'primereact/datatable';
|
||||||
import { Column } from 'primereact/column';
|
import { Column } from 'primereact/column';
|
||||||
import useWindowWidth from '@/hooks/useWindowWidth';
|
|
||||||
import ProgressListItem from '@/components/content/lists/ProgressListItem';
|
import ProgressListItem from '@/components/content/lists/ProgressListItem';
|
||||||
import { formatDateTime } from '@/utils/time';
|
import { formatDateTime } from '@/utils/time';
|
||||||
import { ProgressSpinner } from 'primereact/progressspinner';
|
import { ProgressSpinner } from 'primereact/progressspinner';
|
||||||
|
@ -16,6 +16,7 @@ import dynamic from 'next/dynamic';
|
|||||||
import ZapThreadsWrapper from '@/components/ZapThreadsWrapper';
|
import ZapThreadsWrapper from '@/components/ZapThreadsWrapper';
|
||||||
import appConfig from '@/config/appConfig';
|
import appConfig from '@/config/appConfig';
|
||||||
import useWindowWidth from '@/hooks/useWindowWidth';
|
import useWindowWidth from '@/hooks/useWindowWidth';
|
||||||
|
import MenuTab from '@/components/menutab/MenuTab';
|
||||||
|
|
||||||
const MDDisplay = dynamic(() => import('@uiw/react-markdown-preview'), {
|
const MDDisplay = dynamic(() => import('@uiw/react-markdown-preview'), {
|
||||||
ssr: false,
|
ssr: false,
|
||||||
@ -182,10 +183,22 @@ const Course = () => {
|
|||||||
const [nsec, setNsec] = useState(null);
|
const [nsec, setNsec] = useState(null);
|
||||||
const [npub, setNpub] = useState(null);
|
const [npub, setNpub] = useState(null);
|
||||||
const [sidebarVisible, setSidebarVisible] = useState(false);
|
const [sidebarVisible, setSidebarVisible] = useState(false);
|
||||||
|
const [nAddress, setNAddress] = useState(null);
|
||||||
const windowWidth = useWindowWidth();
|
const windowWidth = useWindowWidth();
|
||||||
const isMobileView = windowWidth <= 968;
|
const isMobileView = windowWidth <= 968;
|
||||||
const [activeTab, setActiveTab] = useState('content'); // Default to content tab on mobile
|
const [activeTab, setActiveTab] = useState('content'); // Default to content tab on mobile
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (router.isReady) {
|
||||||
|
const { slug } = router.query;
|
||||||
|
if (slug.includes('naddr')) {
|
||||||
|
setNAddress(slug);
|
||||||
|
} else {
|
||||||
|
// todo: no naddress?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [router.isReady, router.query.slug]);
|
||||||
|
|
||||||
const setCompleted = useCallback(lessonId => {
|
const setCompleted = useCallback(lessonId => {
|
||||||
setCompletedLessons(prev => [...prev, lessonId]);
|
setCompletedLessons(prev => [...prev, lessonId]);
|
||||||
}, []);
|
}, []);
|
||||||
@ -297,13 +310,93 @@ const Course = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleTab = tab => {
|
const toggleTab = (index) => {
|
||||||
setActiveTab(tab);
|
const tabMap = ['overview', 'content', 'qa'];
|
||||||
if (tab === 'lessons') {
|
// If mobile and we have the lessons tab, insert it at index 2
|
||||||
setSidebarVisible(true);
|
if (isMobileView) {
|
||||||
} else {
|
tabMap.splice(2, 0, 'lessons');
|
||||||
setSidebarVisible(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tabName = tabMap[index];
|
||||||
|
setActiveTab(tabName);
|
||||||
|
|
||||||
|
// Only show/hide sidebar on mobile - desktop keeps sidebar visible
|
||||||
|
if (isMobileView) {
|
||||||
|
if (tabName === 'lessons') {
|
||||||
|
setSidebarVisible(true);
|
||||||
|
} else {
|
||||||
|
setSidebarVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map active tab name back to index for MenuTab
|
||||||
|
const getActiveTabIndex = () => {
|
||||||
|
const tabMap = ['overview', 'content', 'qa'];
|
||||||
|
if (isMobileView) {
|
||||||
|
tabMap.splice(2, 0, 'lessons');
|
||||||
|
}
|
||||||
|
|
||||||
|
return tabMap.indexOf(activeTab);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create tab items for MenuTab
|
||||||
|
const getTabItems = () => {
|
||||||
|
const items = [
|
||||||
|
{
|
||||||
|
label: 'Course Overview',
|
||||||
|
icon: 'pi pi-home',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Lesson Content',
|
||||||
|
icon: 'pi pi-book',
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Add lessons tab only on mobile
|
||||||
|
if (isMobileView) {
|
||||||
|
items.push({
|
||||||
|
label: 'Course Lessons',
|
||||||
|
icon: 'pi pi-list',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
label: 'Q&A',
|
||||||
|
icon: 'pi pi-comments',
|
||||||
|
});
|
||||||
|
|
||||||
|
return items;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Render the QA section (empty for now)
|
||||||
|
const renderQASection = () => {
|
||||||
|
return (
|
||||||
|
<div className="rounded-lg border p-8 mt-4">
|
||||||
|
<h2 className="text-xl font-bold mb-4">Comments</h2>
|
||||||
|
<ZapThreadsWrapper
|
||||||
|
anchor={course?.d}
|
||||||
|
user={session?.user?.pubkey ? nip19.npubEncode(session?.user?.pubkey) : null}
|
||||||
|
relays="wss://nos.lol/, wss://relay.damus.io/, wss://relay.snort.social/, wss://relay.nostr.band/, wss://relay.primal.net/, wss://nostrue.com/, wss://purplerelay.com/, wss://relay.devs.tools/"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Render Course Overview section
|
||||||
|
const renderOverviewSection = () => {
|
||||||
|
return (
|
||||||
|
<div className="bg-gray-900 rounded-lg border border-gray-800 shadow-md p-6">
|
||||||
|
<CourseDetails
|
||||||
|
processedEvent={course}
|
||||||
|
paidCourse={paidCourse}
|
||||||
|
lessons={uniqueLessons}
|
||||||
|
decryptionPerformed={decryptionPerformed}
|
||||||
|
handlePaymentSuccess={handlePaymentSuccess}
|
||||||
|
handlePaymentError={handlePaymentError}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (courseLoading || decryptionLoading) {
|
if (courseLoading || decryptionLoading) {
|
||||||
@ -350,7 +443,7 @@ const Course = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{course && paidCourse !== null && (
|
{/* {course && paidCourse !== null && (
|
||||||
<CourseDetails
|
<CourseDetails
|
||||||
processedEvent={course}
|
processedEvent={course}
|
||||||
paidCourse={paidCourse}
|
paidCourse={paidCourse}
|
||||||
@ -359,71 +452,88 @@ const Course = () => {
|
|||||||
handlePaymentSuccess={handlePaymentSuccess}
|
handlePaymentSuccess={handlePaymentSuccess}
|
||||||
handlePaymentError={handlePaymentError}
|
handlePaymentError={handlePaymentError}
|
||||||
/>
|
/>
|
||||||
)}
|
)} */}
|
||||||
|
|
||||||
<div className="mx-4">
|
<div className="mx-4 mb-12">
|
||||||
{/* Mobile tab navigation */}
|
{/* Tab navigation using MenuTab component */}
|
||||||
{isMobileView && (
|
<div className="sticky top-0 z-10 pt-2 bg-transparent border-b border-gray-700">
|
||||||
<div className="flex w-full border-b border-gray-200 dark:border-gray-700 mb-4">
|
<MenuTab
|
||||||
<button
|
items={getTabItems()}
|
||||||
className={`flex-1 py-3 font-medium text-center border-b-2 ${
|
activeIndex={getActiveTabIndex()}
|
||||||
activeTab === 'lessons'
|
onTabChange={(index) => toggleTab(index)}
|
||||||
? 'border-blue-500 text-blue-600 dark:text-blue-400'
|
|
||||||
: 'border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300'
|
|
||||||
}`}
|
|
||||||
onClick={() => toggleTab('lessons')}
|
|
||||||
>
|
|
||||||
Course Lessons
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className={`flex-1 py-3 font-medium text-center border-b-2 ${
|
|
||||||
activeTab === 'content'
|
|
||||||
? 'border-blue-500 text-blue-600 dark:text-blue-400'
|
|
||||||
: 'border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300'
|
|
||||||
}`}
|
|
||||||
onClick={() => toggleTab('content')}
|
|
||||||
>
|
|
||||||
Lesson Content
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="flex relative">
|
|
||||||
{/* Course Sidebar Component */}
|
|
||||||
<CourseSidebar
|
|
||||||
lessons={uniqueLessons}
|
|
||||||
activeIndex={activeIndex}
|
|
||||||
onLessonSelect={handleLessonSelect}
|
|
||||||
completedLessons={completedLessons}
|
|
||||||
isMobileView={isMobileView}
|
|
||||||
onClose={() => {
|
|
||||||
setSidebarVisible(false);
|
|
||||||
if (isMobileView) setActiveTab('content');
|
|
||||||
}}
|
|
||||||
sidebarVisible={sidebarVisible}
|
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Main content */}
|
<div className="flex items-start mt-4">
|
||||||
|
{/* Main content area - keep existing implementation */}
|
||||||
<div
|
<div
|
||||||
className={`transition-all duration-200 ${
|
className={`transition-all duration-300 ${
|
||||||
!isMobileView ? 'ml-8 flex-1' : activeTab === 'content' ? 'w-full' : 'w-full hidden'
|
isMobileView ? 'w-full' : ((!isMobileView && sidebarVisible) ? 'flex-1' : 'w-full')
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{uniqueLessons.length > 0 && uniqueLessons[activeIndex] ? (
|
{/* Overview tab content */}
|
||||||
<div className="bg-white dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm overflow-hidden">
|
<div className={`${activeTab === 'overview' ? 'block' : 'hidden'}`}>
|
||||||
{renderLesson(uniqueLessons[activeIndex])}
|
{renderOverviewSection()}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
|
||||||
<div className="text-center bg-white dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-700 p-8">
|
|
||||||
<p>Select a lesson from the sidebar to begin learning.</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{course?.content && (
|
{/* Content tab content */}
|
||||||
<div className="mt-8 bg-white dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm">
|
<div className={`${activeTab === 'content' ? 'block' : 'hidden'}`}>
|
||||||
<MDDisplay className="p-4 rounded-lg" source={course.content} />
|
{uniqueLessons.length > 0 && uniqueLessons[activeIndex] ? (
|
||||||
|
<div className="bg-white dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm overflow-hidden">
|
||||||
|
{renderLesson(uniqueLessons[activeIndex])}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-center bg-white dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-700 p-8">
|
||||||
|
<p>Select a lesson from the sidebar to begin learning.</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{course?.content && (
|
||||||
|
<div className="mt-8 bg-white dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm">
|
||||||
|
<MDDisplay className="p-4 rounded-lg" source={course.content} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Lessons tab - only visible on mobile */}
|
||||||
|
<div className={`${activeTab === 'lessons' && isMobileView ? 'block' : 'hidden'}`}>
|
||||||
|
<div className="text-center bg-white dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-700 p-8">
|
||||||
|
<p>Please use the sidebar to navigate lessons.</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
|
||||||
|
{/* QA tab content */}
|
||||||
|
<div className={`${activeTab === 'qa' ? 'block' : 'hidden'}`}>
|
||||||
|
{renderQASection()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Course Sidebar Component - Always visible on desktop, hidden on mobile unless lessons tab is active */}
|
||||||
|
<div
|
||||||
|
className={`flex-shrink-0 transition-all duration-300 ${
|
||||||
|
(!isMobileView && sidebarVisible) ? 'ml-5 w-auto opacity-100' :
|
||||||
|
(isMobileView && activeTab === 'lessons') ? 'ml-0 w-auto opacity-100' :
|
||||||
|
'w-0 ml-0 opacity-0 overflow-hidden'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<CourseSidebar
|
||||||
|
lessons={uniqueLessons}
|
||||||
|
activeIndex={activeIndex}
|
||||||
|
onLessonSelect={(index) => {
|
||||||
|
handleLessonSelect(index);
|
||||||
|
if (isMobileView) {
|
||||||
|
toggleTab(getTabItems().findIndex(item => item.label === 'Lesson Content')); // On mobile, switch to content tab when a lesson is selected
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
completedLessons={completedLessons}
|
||||||
|
isMobileView={isMobileView}
|
||||||
|
onClose={() => {
|
||||||
|
setSidebarVisible(false);
|
||||||
|
setActiveTab('content');
|
||||||
|
}}
|
||||||
|
sidebarVisible={sidebarVisible || !isMobileView} // Always visible on desktop
|
||||||
|
setSidebarVisible={setSidebarVisible}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user