-
Notifications
You must be signed in to change notification settings - Fork 650
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor training pages to allow rendering on client-side instead of … #5523
Changes from all commits
a82227e
fdcbb14
29691f9
f8ad43f
3ae4caf
060c87c
8b16761
d7972f2
fa7083d
6e4b93c
4a3d859
a5ee692
a95377f
ab6e2e3
d4dcc24
1de53f1
970ada0
3f9188d
56afa0f
a0b8f7b
c746f6d
6e043ac
58697fb
512b3d7
dea7937
234335e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import React from 'react'; | ||
|
||
const SearchResults = ({ slides, message }) => { | ||
if (slides && slides.length > 0) { | ||
return ( | ||
<ul className="training-libraries no-bullets no-margin action-card-text"> | ||
{slides.map((slide, index) => ( | ||
<li key={index}> | ||
<a href={`https://dashboard.wikiedu.org/training/${slide.path}`} target="_blank"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. don't think this is correct? The root url will not always be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi, while I didn't make any changes on this and not very sure, I think it will always be that url because the slides controller that has the data is using the TrainingSlide model. |
||
<span dangerouslySetInnerHTML={{ __html: slide.title }} /> ({slide.module_name}) | ||
</a> | ||
<br /> | ||
<div dangerouslySetInnerHTML={{ __html: slide.excerpt }} /> | ||
</li> | ||
))} | ||
</ul> | ||
); | ||
} return ( | ||
<ul className="training-libraries no-bullets no-margin action-card-text"> | ||
<li>{message}</li> | ||
</ul> | ||
); | ||
}; | ||
|
||
export default SearchResults; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
import React, { useState, useEffect } from 'react'; | ||
import { useSelector, useDispatch } from 'react-redux'; | ||
import SearchResults from './search_results.jsx'; | ||
import { fetchTrainingLibraries, searchTrainingLibraries } from '../../actions/training_actions'; | ||
|
||
const TrainingLibraries = () => { | ||
const [libraries, setLibraries] = useState([]); | ||
const focusedLibrarySlug = useSelector(state => state.training.focusedLibrarySlug); | ||
const [search, setSearch] = useState(''); | ||
const [showSearchResults, setShowSearchResults] = useState(false); | ||
const [slides, setSlides] = useState([]); | ||
const [loading, setLoading] = useState(true); | ||
const [noLibraries, setNoLibraries] = useState(false); | ||
const dispatch = useDispatch(); | ||
|
||
useEffect(() => { | ||
dispatch(fetchTrainingLibraries()) | ||
.then((data) => { | ||
const loadedLibraries = data.data.libraries; | ||
setLibraries(loadedLibraries); | ||
setLoading(false); | ||
if (!loadedLibraries.length) { | ||
setNoLibraries(true); | ||
} | ||
}); | ||
}, [dispatch]); | ||
|
||
const handleSearch = (e) => { | ||
setSearch(e.target.value); | ||
}; | ||
|
||
const handleSubmit = (e) => { | ||
e.preventDefault(); | ||
if (search) { | ||
dispatch(searchTrainingLibraries(search)) | ||
.then((data) => { | ||
setSlides(data.data); | ||
setShowSearchResults(true); | ||
}); | ||
} | ||
}; | ||
|
||
let content = null; | ||
|
||
if (loading) { | ||
content = ( | ||
<div className="container"> | ||
<div className="loading__spinner" /> | ||
</div> | ||
); | ||
} else if (noLibraries) { | ||
content = Features.wikiEd ? ( | ||
<div> | ||
<p | ||
dangerouslySetInnerHTML={{ | ||
__html: I18n.t('training.no_training_library_records_wiki_ed_mode', { | ||
url: '/reload_trainings?module=all', | ||
}), | ||
}} | ||
/> | ||
</div> | ||
) : ( | ||
<div>{I18n.t('training.no_training_library_records_non_wiki_ed_mode')}</div> | ||
); | ||
} else { | ||
content = ( | ||
<div> | ||
<h1>Training Libraries</h1> | ||
<div className="search-bar" style={{ position: 'relative' }}> | ||
<form onClick={handleSubmit} acceptCharset="UTF-8" method="get"> | ||
<input name="utf8" type="hidden" defaultValue="✓" /> | ||
<input | ||
type="text" | ||
value={search} | ||
id="search_training" | ||
name="search_training" | ||
onChange={e => handleSearch(e)} | ||
placeholder={I18n.t('training.search_training_resources')} | ||
style={{ width: '100%', height: '3rem', fontSize: '15px' }} | ||
/> | ||
<button type="submit" id="training_search_button" onClick={handleSubmit} style={{ position: 'absolute', right: '20px', top: '10px' }}> | ||
<i className="icon icon-search" /> | ||
</button> | ||
</form> | ||
</div> | ||
{showSearchResults ? ( | ||
<SearchResults slides={slides} message={I18n.t('training.no_training_resource_match_your_search')} /> | ||
) : ( | ||
<ul className="training-libraries no-bullets no-margin"> | ||
{libraries | ||
.filter(library => !library.exclude_from_index) | ||
.map((library, index) => { | ||
const isFocused = focusedLibrarySlug === library.slug; | ||
let libraryClass = 'training-libraries__individual-library no-left-margin'; | ||
if (isFocused) { | ||
libraryClass += ' training-library-focus'; | ||
} else if (focusedLibrarySlug) { | ||
libraryClass += ' training-library-defocus'; | ||
} | ||
|
||
return ( | ||
<li key={index} className={libraryClass}> | ||
<a href={`/training/${library.slug}`} className="action-card action-card-index"> | ||
<header className="action-card-header"> | ||
<h3 className="action-card-title">{library.name}</h3> | ||
<span className="icon-container"> | ||
<i className="action-card-icon icon icon-rt_arrow" /> | ||
</span> | ||
</header> | ||
</a> | ||
<div className="action-card-text"> | ||
<h3>Included Modules:</h3> | ||
<ul> | ||
{library.categories.map(category => | ||
category.modules.map((trainingModule, moduleIndex) => ( | ||
<li key={moduleIndex}> | ||
<a href={`/training/${library.slug}/${trainingModule.slug}`} target="_blank"> | ||
{trainingModule.name} | ||
</a> | ||
</li> | ||
)) | ||
)} | ||
</ul> | ||
</div> | ||
</li> | ||
); | ||
})} | ||
</ul> | ||
)} | ||
</div> | ||
); | ||
} | ||
|
||
return <div>{content}</div>; | ||
}; | ||
|
||
export default TrainingLibraries; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import React, { useEffect } from 'react'; | ||
import { useDispatch, useSelector } from 'react-redux'; | ||
import { useParams } from 'react-router-dom'; | ||
import { fetchTrainingLibrary } from '../../actions/training_actions'; | ||
|
||
const TrainingLibrary = () => { | ||
const { library_id } = useParams(); | ||
const library = useSelector(state => state.training.library); | ||
const dispatch = useDispatch(); | ||
const breadcrumbs = JSON.parse(document.getElementById('react_root').getAttribute('breadcrumbs')); | ||
const trainingLibrary = breadcrumbs[0]; | ||
const libraryName = breadcrumbs[1]; | ||
const progress = JSON.parse(document.getElementById('react_root').getAttribute('prog')); | ||
|
||
useEffect(() => { | ||
dispatch(fetchTrainingLibrary(library_id)); | ||
}, [dispatch, library_id]); | ||
|
||
return ( | ||
<> | ||
<div className="container"> | ||
<ol className="breadcrumbs"> | ||
<li> | ||
<a href="/training"><span>{trainingLibrary.name}</span></a> > <span>{libraryName.name}</span> | ||
</li> | ||
</ol> | ||
</div> | ||
<div className="training__section-overview container"> | ||
<section className="training__header"> | ||
<h1>{library.name}</h1> | ||
<p>{library.introduction}</p> | ||
</section> | ||
|
||
{library.categories && library.categories.length > 0 ? ( | ||
<ul className="training__categories"> | ||
{library.categories.map((libCategory, index) => ( | ||
<li key={index}> | ||
<div className="training__category__header"> | ||
<h1 className="h3">{libCategory.title}</h1> | ||
<p>{libCategory.description}</p> | ||
{library.wiki_page && ( | ||
<div className="training__category__source"> | ||
<a href={`https://meta.wikimedia.org/wiki/${library.wiki_page}`}> | ||
{I18n.t('training.view_library_source')} | ||
</a> | ||
</div> | ||
)} | ||
</div> | ||
<ul className="training__categories__modules"> | ||
{libCategory.modules.map((libModule, moduleIndex) => ( | ||
<li key={moduleIndex}> | ||
<a href={`/training/${library.slug}/${libModule.slug}`} className="action-card"> | ||
<header className="action-card-header"> | ||
<h3 className="action-card-title">{libModule.name}</h3> | ||
<span className="pull-right action-card-title__completion1"> | ||
{progress[libModule.slug]} | ||
</span> | ||
<span className="icon-container"> | ||
<i className="action-card-icon icon icon-rt_arrow" /> | ||
</span> | ||
</header> | ||
<p className="action-card-text"> | ||
<span>{libModule.description}</span> | ||
</p> | ||
</a> | ||
</li> | ||
))} | ||
</ul> | ||
</li> | ||
))} | ||
</ul> | ||
) : ( | ||
null | ||
)} | ||
</div> | ||
</> | ||
); | ||
}; | ||
|
||
export default TrainingLibrary; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this can be extracted to a function — its used in a few places even before this PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @TheTrio , sure lemme do that.