11import React , { useMemo , useState , useEffect } from 'react' ;
22import { useParams , Link } from 'react-router-dom' ;
33import {
4- Row , Spinner , Nav , Icon , ModalLayer , Button , Chip , Card ,
4+ Row , Spinner , Nav , Icon , ModalLayer , Button , Chip , Card , Collapsible ,
55} from '@openedx/paragon' ;
66import {
77 Person ,
@@ -16,17 +16,24 @@ import {
1616} from './data/queries' ;
1717import { CourseCardWithEnrollment } from './CourseCard' ;
1818import CourseDetailPage from './CourseDetails' ;
19+ import { useScreenSize } from '../hooks/useScreenSize' ;
1920
2021const LearningPathDetailPage = ( ) => {
22+ const { isSmall } = useScreenSize ( ) ;
2123 const { key } = useParams ( ) ;
2224 const [ selectedCourseKey , setSelectedCourseKey ] = useState ( null ) ;
2325 const [ enrolling , setEnrolling ] = useState ( false ) ;
26+ const [ openCollapsible , setOpenCollapsible ] = useState ( null ) ;
2427
2528 const [ activeTab , setActiveTab ] = useState ( null ) ;
2629 const handleTabSelect = ( selectedKey ) => {
2730 setActiveTab ( selectedKey ) ;
2831 } ;
2932
33+ const handleCollapsibleToggle = ( collapsibleId ) => {
34+ setOpenCollapsible ( openCollapsible === collapsibleId ? null : collapsibleId ) ;
35+ } ;
36+
3037 // Scroll to the top when the component mounts.
3138 useEffect ( ( ) => {
3239 // Add a timeout to ensure DOM updates are complete.
@@ -138,15 +145,18 @@ const LearningPathDetailPage = () => {
138145 // Hero section - same for both full view and enrolled view.
139146 const heroSection = (
140147 < div className = "hero" >
141- < Card orientation = " horizontal" >
148+ < Card orientation = { isSmall ? 'vertical' : ' horizontal' } className = { isSmall ? 'border-0' : '' } >
142149 < Card . Body >
143150 < Card . Section >
144151 < Link to = "/" className = "d-flex align-items-center back-link pl-4" >
145152 < Icon src = { ChevronLeft } />
146153 < span > Go Back</ span >
147154 </ Link >
148155 </ Card . Section >
149- < Card . Section className = "pl-5 pr-6" >
156+ { isSmall && (
157+ < Card . ImageCap src = { image } logoSrc = { orgData . logo } className = "mb-4" />
158+ ) }
159+ < Card . Section className = "px-sm-4 pl-5 pr-6" >
150160 < Chip iconBefore = { FormatListBulleted } className = "lp-chip" > LEARNING PATH</ Chip >
151161 < h1 className = "my-3 mt-4.5" > { displayName } </ h1 >
152162 < p className = "text-muted" >
@@ -155,9 +165,27 @@ const LearningPathDetailPage = () => {
155165 </ p >
156166 </ Card . Section >
157167 </ Card . Body >
158- < Card . ImageCap src = { image } logoSrc = { orgData . logo } />
168+ { ! isSmall && (
169+ < Card . ImageCap src = { image } logoSrc = { orgData . logo } />
170+ ) }
159171 </ Card >
160- < Row className = "my-4 mx-0 px-6 d-flex hero-info lp-hero-info" >
172+ { isSmall && (
173+ < div className = "mx-4" >
174+ < Button
175+ variant = { enrollmentDate ? 'secondary' : 'primary' }
176+ className = "px-3 w-100"
177+ onClick = { handleEnrollClick }
178+ disabled = { enrolling || enrollmentDate }
179+ >
180+ { ( ( ) => {
181+ if ( enrolling ) { return 'Enrolling...' ; }
182+ if ( enrollmentDate ) { return 'Enrolled' ; }
183+ return 'Enroll' ;
184+ } ) ( ) }
185+ </ Button >
186+ </ div >
187+ ) }
188+ < Row className = "my-4 mx-0 px-sm-4 px-5 px-md-6 flex-column flex-md-row align-items-start hero-info lp-hero-info" >
161189 { accessUntilDate && (
162190 < div className = "d-flex" >
163191 < Icon src = { AccessTimeFilled } className = "mr-4 mb-3" />
@@ -199,79 +227,143 @@ const LearningPathDetailPage = () => {
199227 content = (
200228 < div className = "detail-page learning-path-detail-page" >
201229 { heroSection }
202- < div className = "tabs d-flex align-items-center pl-5.5 pr-0" >
203- < Nav
204- variant = "tabs"
205- onSelect = { handleTabSelect }
206- className = "border-bottom-0"
207- activeKey = { activeTab }
208- >
209- < Nav . Item >
210- < Nav . Link eventKey = "about" className = "font-weight-normal" > About</ Nav . Link >
211- </ Nav . Item >
212- < Nav . Item >
213- < Nav . Link eventKey = "courses" className = "font-weight-normal" > Courses</ Nav . Link >
214- </ Nav . Item >
215- { requiredSkills && requiredSkills . length > 0 && (
230+ { ! isSmall && (
231+ < div className = "tabs d-flex align-items-center pl-5.5 pr-0" >
232+ < Nav
233+ variant = "tabs"
234+ onSelect = { handleTabSelect }
235+ className = "border-bottom-0"
236+ activeKey = { activeTab }
237+ >
216238 < Nav . Item >
217- < Nav . Link eventKey = "requirements " className = "font-weight-normal" > Requirements </ Nav . Link >
239+ < Nav . Link eventKey = "about " className = "font-weight-normal" > About </ Nav . Link >
218240 </ Nav . Item >
219- ) }
220- </ Nav >
221- < Button
222- variant = { enrollmentDate ? 'secondary' : 'primary' }
223- className = "ml-auto rounded-0 px-5.5 align-self-stretch"
224- onClick = { handleEnrollClick }
225- disabled = { enrolling || enrollmentDate }
226- >
227- { ( ( ) => {
228- if ( enrolling ) { return 'Enrolling...' ; }
229- if ( enrollmentDate ) { return 'Enrolled' ; }
230- return 'Enroll' ;
231- } ) ( ) }
232- </ Button >
233- </ div >
241+ < Nav . Item >
242+ < Nav . Link eventKey = "courses" className = "font-weight-normal" > Courses</ Nav . Link >
243+ </ Nav . Item >
244+ { requiredSkills && requiredSkills . length > 0 && (
245+ < Nav . Item >
246+ < Nav . Link eventKey = "requirements" className = "font-weight-normal" > Requirements</ Nav . Link >
247+ </ Nav . Item >
248+ ) }
249+ </ Nav >
250+ < Button
251+ variant = { enrollmentDate ? 'secondary' : 'primary' }
252+ className = "ml-auto rounded-0 px-5.5 align-self-stretch"
253+ onClick = { handleEnrollClick }
254+ disabled = { enrolling || enrollmentDate }
255+ >
256+ { ( ( ) => {
257+ if ( enrolling ) { return 'Enrolling...' ; }
258+ if ( enrollmentDate ) { return 'Enrolled' ; }
259+ return 'Enroll' ;
260+ } ) ( ) }
261+ </ Button >
262+ </ div >
263+ ) }
234264 < div className = "py-3 lp-info" >
235- { activeTab === 'about' && (
236- < section id = "about" >
237- < h2 > About</ h2 >
238- < p >
239- { /* eslint-disable-next-line react/no-danger */ }
240- < div dangerouslySetInnerHTML = { { __html : description || 'No description available.' } } />
241- </ p >
242- </ section >
243- ) }
244- { activeTab === 'courses' && (
245- < div id = "courses-section-wrapper" >
246- < section id = "courses" className = "mx-auto" >
247- < h2 > Courses</ h2 >
248- { ! loadingCourses && ! coursesError && ( ! coursesForPath || coursesForPath . length === 0 ) && (
249- < p > No sub-courses found in this learning path.</ p >
250- ) }
251- { ! loadingCourses && ! coursesError && coursesForPath && coursesForPath . length > 0 && (
252- coursesForPath . map ( course => (
253- < div key = { course . id } className = "mb-3" >
254- < CourseCardWithEnrollment
255- course = { course }
256- learningPathId = { key }
257- enrollmentDateInLearningPath = { enrollmentDate }
258- onClick = { ( ) => handleCourseViewButton ( course . id ) }
259- />
260- </ div >
261- ) )
262- ) }
263- </ section >
265+ { isSmall ? (
266+ < div className = "mobile-content px-4" >
267+ < Collapsible
268+ title = "About"
269+ open = { openCollapsible === 'about' }
270+ onToggle = { ( ) => handleCollapsibleToggle ( 'about' ) }
271+ className = "mb-3"
272+ >
273+ < section id = "about" >
274+ { /* eslint-disable-next-line react/no-danger */ }
275+ < div dangerouslySetInnerHTML = { { __html : description || 'No description available.' } } />
276+ </ section >
277+ </ Collapsible >
278+
279+ < Collapsible
280+ title = "Courses"
281+ open = { openCollapsible === 'courses' }
282+ onToggle = { ( ) => handleCollapsibleToggle ( 'courses' ) }
283+ className = "mb-3"
284+ >
285+ < div id = "courses-section-wrapper" >
286+ < section id = "courses" className = "mx-4" >
287+ { ! loadingCourses && ! coursesError && ( ! coursesForPath || coursesForPath . length === 0 ) && (
288+ < p > No sub-courses found in this learning path.</ p >
289+ ) }
290+ { ! loadingCourses && ! coursesError && coursesForPath && coursesForPath . length > 0 && (
291+ coursesForPath . map ( course => (
292+ < div key = { course . id } className = "mb-3" >
293+ < CourseCardWithEnrollment
294+ course = { course }
295+ learningPathId = { key }
296+ enrollmentDateInLearningPath = { enrollmentDate }
297+ onClick = { ( ) => handleCourseViewButton ( course . id ) }
298+ />
299+ </ div >
300+ ) )
301+ ) }
302+ </ section >
303+ </ div >
304+ </ Collapsible >
305+
306+ { requiredSkills && requiredSkills . length > 0 && (
307+ < Collapsible
308+ title = "Requirements"
309+ open = { openCollapsible === 'requirements' }
310+ onToggle = { ( ) => handleCollapsibleToggle ( 'requirements' ) }
311+ className = "mb-3"
312+ >
313+ < section id = "requirements" >
314+ { requiredSkills . map ( ( skillObj ) => (
315+ < p key = { `requirement-${ skillObj . skill . displayName . replace ( / \s + / g, '-' ) . substring ( 0 , 40 ) } ` } >
316+ { skillObj . skill . displayName }
317+ </ p >
318+ ) ) }
319+ </ section >
320+ </ Collapsible >
321+ ) }
322+ </ div >
323+ ) : (
324+ < div className = "desktop-content" >
325+ { activeTab === 'about' && (
326+ < section id = "about" >
327+ < h2 > About</ h2 >
328+ < p >
329+ { /* eslint-disable-next-line react/no-danger */ }
330+ < div dangerouslySetInnerHTML = { { __html : description || 'No description available.' } } />
331+ </ p >
332+ </ section >
333+ ) }
334+ { activeTab === 'courses' && (
335+ < div id = "courses-section-wrapper" >
336+ < section id = "courses" className = "mx-auto" >
337+ < h2 > Courses</ h2 >
338+ { ! loadingCourses && ! coursesError && ( ! coursesForPath || coursesForPath . length === 0 ) && (
339+ < p > No sub-courses found in this learning path.</ p >
340+ ) }
341+ { ! loadingCourses && ! coursesError && coursesForPath && coursesForPath . length > 0 && (
342+ coursesForPath . map ( course => (
343+ < div key = { course . id } className = "mb-3" >
344+ < CourseCardWithEnrollment
345+ course = { course }
346+ learningPathId = { key }
347+ enrollmentDateInLearningPath = { enrollmentDate }
348+ onClick = { ( ) => handleCourseViewButton ( course . id ) }
349+ />
350+ </ div >
351+ ) )
352+ ) }
353+ </ section >
354+ </ div >
355+ ) }
356+ { activeTab === 'requirements' && (
357+ < section id = "requirements" >
358+ < h2 > Requirements</ h2 >
359+ { requiredSkills . map ( ( skillObj ) => (
360+ < p key = { `requirement-${ skillObj . skill . displayName . replace ( / \s + / g, '-' ) . substring ( 0 , 40 ) } ` } >
361+ { skillObj . skill . displayName }
362+ </ p >
363+ ) ) }
364+ </ section >
365+ ) }
264366 </ div >
265- ) }
266- { activeTab === 'requirements' && (
267- < section id = "requirements" >
268- < h2 > Requirements</ h2 >
269- { requiredSkills . map ( ( skillObj ) => (
270- < p key = { `requirement-${ skillObj . skill . displayName . replace ( / \s + / g, '-' ) . substring ( 0 , 40 ) } ` } >
271- { skillObj . skill . displayName }
272- </ p >
273- ) ) }
274- </ section >
275367 ) }
276368 </ div >
277369 </ div >
0 commit comments