Files
edx-platform/lms/static/js/demographics_collection/Wizard.jsx
2020-11-24 10:13:00 -05:00

125 lines
4.2 KiB
JavaScript

/* global gettext */
import React from 'react';
import isFunction from 'lodash/isFunction';
const Page = ({ children }) => children;
const Header = () => null;
const Closer = () => null;
const ErrorPage = () => null;
export default class Wizard extends React.Component {
constructor(props) {
super(props);
this.findSubComponentByType = this.findSubComponentByType.bind(this);
this.handleNext = this.handleNext.bind(this);
this.state = {
currentPage: 1,
totalPages: 0,
pages: [],
wizardContext: {},
}
this.wizardComplete = this.wizardComplete.bind(this);
}
componentDidMount() {
const pages = this.findSubComponentByType(Wizard.Page.name);
const totalPages = pages.length;
const wizardContext = this.props.wizardContext;
const closer = this.findSubComponentByType(Wizard.Closer.name)[0];
pages.push(closer);
this.setState({ pages, totalPages, wizardContext });
}
handleNext() {
if (this.state.currentPage < this.props.children.length) {
this.setState(prevState => ({ currentPage: prevState.currentPage + 1 }))
}
}
findSubComponentByType(type) {
return React.Children.toArray(this.props.children).filter(child => child.type.name === type)
}
// this needs to handle the case of no provided header
renderHeader() {
const header = this.findSubComponentByType(Wizard.Header.name)[0];
return header.props.children({ currentPage: this.state.currentPage, totalPages: this.state.totalPages })
}
renderPage() {
if (this.state.totalPages) {
const page = this.state.pages[this.state.currentPage - 1];
if (page.type.name === Wizard.Closer.name) {
return page.props.children;
}
if (isFunction(page.props.children)) {
return page.props.children({ wizardConsumer: this.props.wizardContext });
} else {
return page.props.children;
}
}
return null;
}
// this needs to handle the case of no provided errorPage
renderError() {
const errorPage = this.findSubComponentByType(Wizard.ErrorPage.name)[0];
return (
<div className="wizard-container" role="dialog" aria-label={gettext("demographics questionnaire")}>
<div className="wizard-header">
{errorPage.props.children}
</div>
<div className="wizard-footer justify-content-end h-100 d-flex flex-column">
<button className="wizard-button colored" arial-label={gettext("close questionnaire")} onClick={this.props.onWizardComplete}>{gettext("Close")}</button>
</div>
</div>
)
}
/**
* Utility method that helps determine if the learner is on the final page of the modal.
*/
onFinalPage() {
return this.state.pages.length === this.state.currentPage;
}
/**
* Utility method for closing the modal and returning the learner back to the Course Dashboard.
* If a learner is on the final page of the modal, meaning they have answered all of the
* questions, clicking the "Return to my dashboard" button will also dismiss the CTA from the
* course dashboard.
*/
async wizardComplete() {
if (this.onFinalPage()) {
this.props.dismissBanner();
}
this.props.onWizardComplete();
}
render() {
const finalPage = this.onFinalPage();
if (this.props.error) {
return this.renderError();
}
return (
<div className="wizard-container" role="dialog" aria-label={gettext("demographics questionnaire")}>
<div className="wizard-header mb-4">
{this.state.totalPages >= this.state.currentPage && this.renderHeader()}
</div>
{this.renderPage()}
<div className="wizard-footer justify-content-end h-100 d-flex flex-column">
<button className={`wizard-button ${finalPage && 'colored'}`} onClick={this.wizardComplete} aria-label={gettext("finish later")}>{finalPage ? gettext("Return to my dashboard") : gettext("Finish later")}</button>
<button className="wizard-button colored" hidden={finalPage} onClick={this.handleNext} aria-label={gettext("next page")}>{gettext("Next")}</button>
</div>
</div>
);
}
}
Wizard.Page = Page;
Wizard.Header = Header;
Wizard.Closer = Closer;
Wizard.ErrorPage = ErrorPage;