Skip to content

Commit

Permalink
feat: add a button to return to studio (openedx#322)
Browse files Browse the repository at this point in the history
  • Loading branch information
KristinAoki authored May 3, 2023
1 parent 6aaedfc commit c49779a
Show file tree
Hide file tree
Showing 12 changed files with 453 additions and 84 deletions.
13 changes: 9 additions & 4 deletions src/editors/EditorPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,13 @@ export const EditorPage = ({
studioEndpointUrl,
onClose,
}) => (
<ErrorBoundary>
<Provider store={store}>
<Provider store={store}>
<ErrorBoundary
{...{
learningContextId: courseId,
studioEndpointUrl,
}}
>
<Editor
{...{
onClose,
Expand All @@ -26,8 +31,8 @@ export const EditorPage = ({
studioEndpointUrl,
}}
/>
</Provider>
</ErrorBoundary>
</ErrorBoundary>
</Provider>
);
EditorPage.defaultProps = {
blockId: null,
Expand Down
5 changes: 5 additions & 0 deletions src/editors/EditorPage.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ const props = {
};
jest.mock('react-redux', () => ({
Provider: 'Provider',
connect: (mapStateToProps, mapDispatchToProps) => (component) => ({
mapStateToProps,
mapDispatchToProps,
component,
}),
}));
jest.mock('./Editor', () => 'Editor');

Expand Down
15 changes: 10 additions & 5 deletions src/editors/VideoSelectorPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,30 @@ import React from 'react';
import { Provider } from 'react-redux';
import PropTypes from 'prop-types';
import ErrorBoundary from './sharedComponents/ErrorBoundary';
import { VideoSelector } from './VideoSelector';
import VideoSelector from './VideoSelector';
import store from './data/store';

const VideoSelectorPage = ({
courseId,
lmsEndpointUrl,
studioEndpointUrl,
}) => (
<ErrorBoundary>
<Provider store={store}>
<Provider store={store}>
<ErrorBoundary
{...{
learningContextId: courseId,
studioEndpointUrl,
}}
>
<VideoSelector
{...{
learningContextId: courseId,
lmsEndpointUrl,
studioEndpointUrl,
}}
/>
</Provider>
</ErrorBoundary>
</ErrorBoundary>
</Provider>
);

VideoSelectorPage.defaultProps = {
Expand Down
5 changes: 5 additions & 0 deletions src/editors/VideoSelectorPage.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ const props = {

jest.mock('react-redux', () => ({
Provider: 'Provider',
connect: (mapStateToProps, mapDispatchToProps) => (component) => ({
mapStateToProps,
mapDispatchToProps,
component,
}),
}));
jest.mock('./VideoSelector', () => 'VideoSelector');

Expand Down
54 changes: 30 additions & 24 deletions src/editors/__snapshots__/EditorPage.test.jsx.snap
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Editor Page snapshots props besides blockType default to null 1`] = `
<ErrorBoundary>
<Provider
store={
Object {
"dispatch": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
Symbol(Symbol.observable): [Function],
}
<Provider
store={
Object {
"dispatch": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
Symbol(Symbol.observable): [Function],
}
}
>
<ErrorBoundary
learningContextId={null}
studioEndpointUrl={null}
>
<Editor
blockId={null}
Expand All @@ -21,22 +24,25 @@ exports[`Editor Page snapshots props besides blockType default to null 1`] = `
onClose={null}
studioEndpointUrl={null}
/>
</Provider>
</ErrorBoundary>
</ErrorBoundary>
</Provider>
`;

exports[`Editor Page snapshots rendering correctly with expected Input 1`] = `
<ErrorBoundary>
<Provider
store={
Object {
"dispatch": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
Symbol(Symbol.observable): [Function],
}
<Provider
store={
Object {
"dispatch": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
Symbol(Symbol.observable): [Function],
}
}
>
<ErrorBoundary
learningContextId="course-v1:edX+DemoX+Demo_Course"
studioEndpointUrl="fakeurl.com"
>
<Editor
blockId="block-v1:edX+DemoX+Demo_Course+type@html+block@030e35c4756a4ddc8d40b95fbbfff4d4"
Expand All @@ -46,6 +52,6 @@ exports[`Editor Page snapshots rendering correctly with expected Input 1`] = `
onClose={[MockFunction props.onClose]}
studioEndpointUrl="fakeurl.com"
/>
</Provider>
</ErrorBoundary>
</ErrorBoundary>
</Provider>
`;
58 changes: 32 additions & 26 deletions src/editors/__snapshots__/VideoSelectorPage.test.jsx.snap
Original file line number Diff line number Diff line change
@@ -1,45 +1,51 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Video Selector Page snapshots rendering correctly with expected Input 1`] = `
<ErrorBoundary>
<Provider
store={
Object {
"dispatch": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
Symbol(Symbol.observable): [Function],
}
<Provider
store={
Object {
"dispatch": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
Symbol(Symbol.observable): [Function],
}
}
>
<ErrorBoundary
learningContextId="course-v1:edX+DemoX+Demo_Course"
studioEndpointUrl="fakeurl.com"
>
<Component
<VideoSelector
learningContextId="course-v1:edX+DemoX+Demo_Course"
lmsEndpointUrl="evenfakerurl.com"
studioEndpointUrl="fakeurl.com"
/>
</Provider>
</ErrorBoundary>
</ErrorBoundary>
</Provider>
`;

exports[`Video Selector Page snapshots rendering with props to null 1`] = `
<ErrorBoundary>
<Provider
store={
Object {
"dispatch": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
Symbol(Symbol.observable): [Function],
}
<Provider
store={
Object {
"dispatch": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
Symbol(Symbol.observable): [Function],
}
}
>
<ErrorBoundary
learningContextId={null}
studioEndpointUrl={null}
>
<Component
<VideoSelector
learningContextId={null}
lmsEndpointUrl={null}
studioEndpointUrl={null}
/>
</Provider>
</ErrorBoundary>
</ErrorBoundary>
</Provider>
`;
81 changes: 61 additions & 20 deletions src/editors/sharedComponents/ErrorBoundary/ErrorPage.jsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,89 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import {
Button, Container, Row, Col,
} from '@edx/paragon';

import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import messages from './messages';
import { navigateTo } from '../../hooks';
import { selectors } from '../../data/redux';

/**
* An error page that displays a generic message for unexpected errors. Also contains a "Try
* Again" button to refresh the page.
*/
export const ErrorPage = ({
message,
studioEndpointUrl,
learningContextId,
// redux
unitData,
// injected
intl,
}) => (
<Container fluid className="py-5 justify-content-center align-items-start text-center">
<Row>
<Col>
<p className="text-muted">
{intl.formatMessage(messages.unexpectedError)}
</p>
{message && (
<div role="alert" className="my-4">
<p>{message}</p>
</div>
)}
<Button onClick={global.location.reload()}>
{intl.formatMessage(messages.unexpectedErrorButtonLabel)}
</Button>
</Col>
</Row>
</Container>
);
}) => {
const outlineType = learningContextId?.startsWith('library-v1') ? 'library' : 'course';
const outlineUrl = `${studioEndpointUrl}/${outlineType}/${learningContextId}`;
const unitUrl = unitData?.data ? `${studioEndpointUrl}/container/${unitData?.data.ancestors[0].id}` : null;

return (
<Container fluid className="py-5 justify-content-center align-items-start text-center">
<Row>
<Col>
<p className="text-muted">
{intl.formatMessage(messages.unexpectedError)}
</p>
{message && (
<div role="alert" className="my-4">
<p>{message}</p>
</div>
)}
<Row className="justify-content-center">
{learningContextId && (unitUrl && outlineType !== 'library' ? (
<Button className="mr-2" variant="outline-primary" onClick={() => navigateTo(unitUrl)}>
{intl.formatMessage(messages.returnToUnitPageLabel)}
</Button>
) : (
<Button className="mr-2" variant="outline-primary" onClick={() => navigateTo(outlineUrl)}>
{intl.formatMessage(messages.returnToOutlineLabel, { outlineType })}
</Button>
))}
<Button className="ml-2" onClick={() => global.location.reload()}>
{intl.formatMessage(messages.unexpectedErrorButtonLabel)}
</Button>
</Row>
</Col>
</Row>
</Container>
);
};

ErrorPage.propTypes = {
message: PropTypes.string,
learningContextId: PropTypes.string.isRequired,
studioEndpointUrl: PropTypes.string.isRequired,
// redux
unitData: PropTypes.shape({
data: PropTypes.shape({
ancestors: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string,
}),
),
}),
}),
// injected
intl: intlShape.isRequired,
};

ErrorPage.defaultProps = {
message: null,
unitData: null,
};

export default injectIntl(ErrorPage);
export const mapStateToProps = (state) => ({
unitData: selectors.app.unitUrl(state),
});

export default injectIntl(connect(mapStateToProps)(ErrorPage));
Loading

0 comments on commit c49779a

Please sign in to comment.