-
Notifications
You must be signed in to change notification settings - Fork 19
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
feat: ENT-7367 Added skills quiz v2 #911
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
import React, { useContext, useState, useEffect } from 'react'; | ||
import { | ||
SelectableBox, Chip, Spinner, Stack, Button, | ||
} from '@edx/paragon'; | ||
import PropTypes from 'prop-types'; | ||
import { SkillsContext } from '../skills-quiz/SkillsContextProvider'; | ||
import { SET_KEY_VALUE } from '../skills-quiz/data/constants'; | ||
import { DROPDOWN_OPTION_IMPROVE_CURRENT_ROLE } from '../skills-quiz/constants'; | ||
import TopSkillsOverview from '../skills-quiz/TopSkillsOverview'; | ||
import SearchCourseCard from '../skills-quiz/SearchCourseCard'; | ||
import SearchProgramCard from '../skills-quiz/SearchProgramCard'; | ||
import SearchPathways from '../skills-quiz/SearchPathways'; | ||
import SkillsCourses from '../skills-quiz/SkillsCourses'; | ||
|
||
const JobCardComponent = ({ | ||
jobs, isLoading, jobIndex, courseIndex, | ||
}) => { | ||
const { dispatch, state } = useContext(SkillsContext); | ||
const { goal } = state; | ||
const [jobSelected, setJobSelected] = useState(undefined); | ||
const [showMoreRecommendedCourses, setShowMoreRecommendedCourses] = useState(false); | ||
|
||
useEffect(() => { | ||
if (jobs?.length === 1) { | ||
setJobSelected(jobs[0]?.name); | ||
dispatch({ type: SET_KEY_VALUE, key: 'selectedJob', value: jobSelected }); | ||
} else if (jobs?.length === 0) { | ||
setJobSelected(undefined); | ||
dispatch({ type: SET_KEY_VALUE, key: 'selectedJob', value: undefined }); | ||
} | ||
}, [jobs, dispatch, jobSelected]); | ||
|
||
const handleChange = (e) => { | ||
setJobSelected(e.target.value); | ||
dispatch({ type: SET_KEY_VALUE, key: 'selectedJob', value: e.target.value }); | ||
setShowMoreRecommendedCourses(false); | ||
}; | ||
|
||
return !isLoading ? ( | ||
<> | ||
<SelectableBox.Set | ||
type="radio" | ||
value={jobSelected} | ||
onChange={handleChange} | ||
name="industry" | ||
columns="3" | ||
className="selectable-box mt-4" | ||
> | ||
{jobs.map((job) => ( | ||
<SelectableBox | ||
key={job.id} | ||
className="box" | ||
value={job.name} | ||
inputHidden={false} | ||
type="radio" | ||
aria-label={job.name} | ||
isLoading={isLoading} | ||
> | ||
<div> | ||
<div className="lead">{job.name}</div> | ||
<div className="x-small mt-3">Related skills</div> | ||
{job.skills.slice(0, 5).map((skill) => ( | ||
<div key={skill.name}> | ||
<Chip>{skill.name}</Chip> | ||
</div> | ||
))} | ||
</div> | ||
</SelectableBox> | ||
))} | ||
</SelectableBox.Set> | ||
{(jobSelected || goal === DROPDOWN_OPTION_IMPROVE_CURRENT_ROLE) && ( | ||
<> | ||
<TopSkillsOverview index={jobIndex} /> | ||
<Stack gap={4}> | ||
<SearchCourseCard index={courseIndex} /> | ||
<SearchProgramCard index={courseIndex} /> | ||
<SearchPathways index={courseIndex} /> | ||
</Stack> | ||
<div className="text-center py-4"> | ||
{ !showMoreRecommendedCourses && ( | ||
<Button | ||
variant="outline-primary" | ||
onClick={() => setShowMoreRecommendedCourses(true)} | ||
> | ||
See more course recommendations | ||
</Button> | ||
) } | ||
</div> | ||
{ showMoreRecommendedCourses && <SkillsCourses index={courseIndex} />} | ||
</> | ||
)} | ||
</> | ||
) : ( | ||
<Spinner | ||
animation="border" | ||
className="mie-3 d-block mt-4" | ||
screenReaderText="loading" | ||
/> | ||
); | ||
}; | ||
|
||
JobCardComponent.defaultProps = { | ||
jobs: undefined, | ||
isLoading: false, | ||
jobIndex: undefined, | ||
courseIndex: undefined, | ||
}; | ||
|
||
JobCardComponent.propTypes = { | ||
isLoading: PropTypes.bool, | ||
jobs: PropTypes.arrayOf(PropTypes.shape()), | ||
jobIndex: PropTypes.shape({ | ||
appId: PropTypes.string, | ||
indexName: PropTypes.string, | ||
search: PropTypes.func.isRequired, | ||
}), | ||
courseIndex: PropTypes.shape({ | ||
appId: PropTypes.string, | ||
indexName: PropTypes.string, | ||
search: PropTypes.func.isRequired, | ||
}), | ||
}; | ||
|
||
export default JobCardComponent; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { Card, useMediaQuery, breakpoints } from '@edx/paragon'; | ||
import PropTypes from 'prop-types'; | ||
|
||
const ProgramCard = ({ | ||
mainImg, logoImg, title, subtitle, | ||
}) => { | ||
const isExtraSmall = useMediaQuery({ maxWidth: breakpoints.small.maxWidth }); | ||
return ( | ||
<Card isClickable style={{ width: isExtraSmall ? '100%' : '40%' }}> | ||
<Card.ImageCap | ||
src={mainImg} | ||
srcAlt="Card image" | ||
logoSrc={logoImg} | ||
logoAlt="Card logo" | ||
/> | ||
<Card.Header title={title} subtitle={subtitle} /> | ||
</Card> | ||
); | ||
}; | ||
|
||
ProgramCard.propTypes = { | ||
title: PropTypes.string, | ||
subtitle: PropTypes.string, | ||
mainImg: PropTypes.string, | ||
logoImg: PropTypes.string, | ||
}; | ||
|
||
ProgramCard.defaultProps = { | ||
title: null, | ||
subtitle: null, | ||
mainImg: null, | ||
logoImg: null, | ||
}; | ||
|
||
export default ProgramCard; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import { Helmet } from 'react-helmet'; | ||
import './styles/index.scss'; | ||
import { AppContext } from '@edx/frontend-platform/react'; | ||
import PropTypes from 'prop-types'; | ||
import { | ||
ModalDialog, useToggle, ActionRow, Button, | ||
} from '@edx/paragon'; | ||
import { useHistory } from 'react-router-dom'; | ||
import { useContext } from 'react'; | ||
import { | ||
SKILL_BUILDER_TITLE, | ||
text, | ||
webTechBootCamps, | ||
closeModalText, | ||
} from './constants'; | ||
import ProgramCard from './ProgramCard'; | ||
import SkillsQuizHeader from './SkillsQuizHeader'; | ||
import SkillQuizForm from './SkillsQuizForm'; | ||
import headerImage from '../skills-quiz/images/headerImage.png'; | ||
|
||
const SkillsQuizV2 = ({ isStyleAutoSuggest }) => { | ||
const { enterpriseConfig } = useContext(AppContext); | ||
const history = useHistory(); | ||
const [isOpen, open, close] = useToggle(false); | ||
|
||
const handleExit = () => { | ||
history.push(`/${enterpriseConfig.slug}/search`); | ||
}; | ||
|
||
const TITLE = `edx - ${SKILL_BUILDER_TITLE}`; | ||
return ( | ||
<> | ||
<Helmet title={TITLE} /> | ||
<ModalDialog | ||
className="modal-small" | ||
title="Close Dialog" | ||
isOpen={isOpen} | ||
onClose={close} | ||
size="sm" | ||
hasCloseButton={false} | ||
> | ||
<ModalDialog.Header> | ||
<ModalDialog.Title>Exit Skill Builder?</ModalDialog.Title> | ||
</ModalDialog.Header> | ||
<ModalDialog.Body> | ||
<p className="text-justify">{closeModalText}</p> | ||
<ActionRow className="mt-4.5"> | ||
<Button variant="tertiary" onClick={close}> | ||
Back to Skill Builder | ||
</Button> | ||
<Button variant="primary" onClick={() => handleExit()}> | ||
Exit | ||
</Button> | ||
</ActionRow> | ||
</ModalDialog.Body> | ||
</ModalDialog> | ||
|
||
<ModalDialog | ||
title="Skills Quiz" | ||
size="fullscreen" | ||
className="bg-light-200 skills-quiz-modal" | ||
isOpen | ||
onClose={open} | ||
> | ||
<ModalDialog.Hero className="md-img"> | ||
<ModalDialog.Hero.Background backgroundSrc={headerImage} /> | ||
<ModalDialog.Hero.Content style={{ maxWidth: '15rem' }}> | ||
<SkillsQuizHeader /> | ||
</ModalDialog.Hero.Content> | ||
</ModalDialog.Hero> | ||
<ModalDialog.Body> | ||
<div className="page-body"> | ||
<div className="text"> | ||
<p className="text-gray-600 text-justify">{text}</p> | ||
</div> | ||
<SkillQuizForm isStyleAutoSuggest={isStyleAutoSuggest} /> | ||
<div className="cards-display"> | ||
<p className="pgn__form-label"> | ||
Boot camps for a web technology specialist | ||
</p> | ||
<div className="card-container"> | ||
{webTechBootCamps.map((bootcamp) => ( | ||
<ProgramCard {...bootcamp} /> | ||
))} | ||
</div> | ||
</div> | ||
</div> | ||
</ModalDialog.Body> | ||
</ModalDialog> | ||
</> | ||
); | ||
}; | ||
|
||
SkillsQuizV2.propTypes = { | ||
isStyleAutoSuggest: PropTypes.bool, | ||
}; | ||
|
||
SkillsQuizV2.defaultProps = { | ||
isStyleAutoSuggest: false, | ||
}; | ||
|
||
export default SkillsQuizV2; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import { Button } from '@edx/paragon'; | ||
import { useState, useMemo } from 'react'; | ||
import { getConfig } from '@edx/frontend-platform/config'; | ||
import algoliasearch from 'algoliasearch/lite'; | ||
import { InstantSearch } from 'react-instantsearch-dom'; | ||
import PropTypes from 'prop-types'; | ||
import SearchJobDropdown from '../skills-quiz/SearchJobDropdown'; | ||
import CurrentJobDropdown from '../skills-quiz/CurrentJobDropdown'; | ||
import IndustryDropdown from '../skills-quiz/IndustryDropdown'; | ||
import GoalDropdown from '../skills-quiz/GoalDropdown'; | ||
import SearchJobCard from '../skills-quiz/SearchJobCard'; | ||
|
||
const SkillQuizForm = ({ isStyleAutoSuggest }) => { | ||
const config = getConfig(); | ||
|
||
const [searchClient, courseIndex, jobIndex] = useMemo(() => { | ||
const client = algoliasearch( | ||
config.ALGOLIA_APP_ID, | ||
config.ALGOLIA_SEARCH_API_KEY, | ||
); | ||
const cIndex = client.initIndex(config.ALGOLIA_INDEX_NAME); | ||
const jIndex = client.initIndex(config.ALGOLIA_INDEX_NAME_JOBS); | ||
return [client, cIndex, jIndex]; | ||
}, [ | ||
config.ALGOLIA_APP_ID, | ||
config.ALGOLIA_INDEX_NAME, | ||
config.ALGOLIA_INDEX_NAME_JOBS, | ||
config.ALGOLIA_SEARCH_API_KEY, | ||
]); | ||
const [hide, setHide] = useState(true); | ||
|
||
return ( | ||
<div className="form"> | ||
<InstantSearch | ||
indexName={config.ALGOLIA_INDEX_NAME_JOBS} | ||
searchClient={searchClient} | ||
> | ||
<p className="mt-4 font-weight-bold"> | ||
What roles are you interested in ? | ||
</p> | ||
<SearchJobDropdown key="search" isStyleSearchBox isChip /> | ||
<Button | ||
variant="link" | ||
size="inline" | ||
className="mb-2 mb-sm-0 btn" | ||
onClick={() => setHide(!hide)} | ||
> | ||
{!hide ? 'Hide advanced options' : 'Show advanced options'} | ||
</Button> | ||
{!hide && ( | ||
<div> | ||
<p className="mt-4 font-weight-bold"> | ||
Tell us about what you want to achieve ? | ||
</p> | ||
<GoalDropdown key="goal" /> | ||
<p className="mt-4 font-weight-bold"> | ||
Search and select your current job title | ||
</p> | ||
<CurrentJobDropdown | ||
key="current" | ||
isStyleAutoSuggest={isStyleAutoSuggest} | ||
/> | ||
|
||
<p className="mt-4 font-weight-bold"> | ||
What industry are you interested in ? | ||
</p> | ||
<IndustryDropdown key="industry" isStyleSearchBox /> | ||
</div> | ||
)} | ||
<SearchJobCard index={jobIndex} courseIndex={courseIndex} isSkillQuizV2 /> | ||
</InstantSearch> | ||
</div> | ||
); | ||
}; | ||
|
||
SkillQuizForm.propTypes = { | ||
isStyleAutoSuggest: PropTypes.bool, | ||
}; | ||
|
||
SkillQuizForm.defaultProps = { | ||
isStyleAutoSuggest: false, | ||
}; | ||
|
||
export default SkillQuizForm; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import React from 'react'; | ||
import edxLogo from '../skills-quiz/images/edx-logo.svg'; | ||
|
||
const SkillsQuizHeader = () => ( | ||
<div style={{ display: 'flex' }} className="ml-2"> | ||
<img src={edxLogo} alt="edx-logo" height="110px" /> | ||
<div className="ml-5 vertical-line" /> | ||
<div style={{ minWidth: 'max-content' }} className="ml-5 header-text"> | ||
<h1 className="heading">Skills builder</h1> | ||
<h1 className="subheading-v2 text-light-500">Let edX be your guide</h1> | ||
</div> | ||
</div> | ||
); | ||
|
||
export default SkillsQuizHeader; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
export const SKILL_BUILDER_TITLE = 'Skill Builder'; | ||
|
||
export const text = 'We combine the educational expertise with labor market data to help you reach your learning and professional goals. Whether you are looking to grow in your career, change careers, or just learn new skills, this tool can help you find a relevant course. Your role selection and recommendations are private and are not visible to your edX administrator'; | ||
|
||
export const webTechBootCamps = [ | ||
{ | ||
mainImg: | ||
'https://prod-discovery.edx-cdn.org/media/course/image/8e119d15-b484-4de6-a795-b6d9be101233-19d5867a8db3.small.png', | ||
logoImg: | ||
'https://prod-discovery.edx-cdn.org/organization/logos/eac96c61-1462-4084-a0b2-12525b74a9e1-8377159ff774.png', | ||
title: 'Engineering for Your Classroom K – 3', | ||
subtitle: 'University of British Columbia', | ||
}, | ||
{ | ||
mainImg: | ||
'https://prod-discovery.edx-cdn.org/media/course/image/7868fb19-176b-4d98-b1a0-4d1e2029fdb8-b302dd3a98d1.jpg', | ||
logoImg: | ||
'https://prod-discovery.edx-cdn.org/organization/logos/eac96c61-1462-4084-a0b2-12525b74a9e1-8377159ff774.png', | ||
title: 'Software Engineering : Introduction', | ||
subtitle: 'University of British Columbia', | ||
}, | ||
]; | ||
export const closeModalText = 'Learners who enroll in courses that align with their career goals are more likely to complete the course'; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
[inform] Current convention in this MFE is to import component stylesheets within the top-level
src/index.scss
file as opposed to within the component's JS itself as done here.Rationale: by importing through JS as done here, the SCSS cannot make use of the existing SCSS theme variables without importing duplicate variable definitions into this stylesheet first. By importing this SCSS file within
src/index.scss
instead, the skills quiz v2 SCSS can make use of Paragon theme variables.For example, the styles for the original skills quiz is imported within
src/index.scss
here.