Skip to content

Commit

Permalink
feat: implemented error handling mockup (#663)
Browse files Browse the repository at this point in the history
* feat: implemented error handling mockup

* fix: fixed incontextsidebar issues

* fix: fixed discussion home  test cases

* fix: fixed dicussion home test cases

* refactor: added  code review fixes

---------

Co-authored-by: Awais Ansari <[email protected]>
  • Loading branch information
sundasnoreen12 and awais-ansari authored Feb 21, 2024
1 parent f0a4586 commit ac17fd7
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 63 deletions.
14 changes: 14 additions & 0 deletions src/assets/ContentUnavailable.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const ContentUnavailable = () => (
<svg width="229" height="167" viewBox="0 0 229 167" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.9664 67.649C1.9299 88.4776 -5.31519 112.805 4.55784 135.123C22.5467 175.788 120.573 164.359 163.26 148.39C283.487 103.415 225.675 -14.6 95.6636 14.5816C59.2626 22.7519 30.003 46.8204 15.9664 67.649Z" fill="#E1DDDB" fillOpacity="0.3" />
<path d="M101.264 120.672L101.13 120.486H100.9H58.4969C54.0932 120.486 50.45 116.531 50.45 111.548V60.3944C50.45 55.4164 54.0937 51.45 58.4969 51.45H170.468C174.872 51.45 178.522 55.4171 178.55 60.3969V111.548C178.55 116.531 174.901 120.486 170.497 120.486H126.838H126.636L126.502 120.637L112.568 136.283L101.264 120.672Z" fill="white" stroke="#454545" strokeWidth="0.9" />
<path d="M99.363 99.6098L93.9175 94.6162L82.0459 107.565L87.4913 112.558L99.363 99.6098Z" fill="#002121" />
<path d="M87.3976 112.877C87.3486 112.862 87.3041 112.836 87.268 112.8L81.7927 107.803C81.76 107.774 81.7334 107.739 81.7145 107.7C81.6956 107.661 81.6848 107.619 81.6828 107.575C81.6807 107.532 81.6874 107.488 81.7025 107.447C81.7175 107.407 81.7407 107.369 81.7705 107.338L93.645 94.3885C93.6737 94.3558 93.7088 94.3292 93.7481 94.3106C93.7875 94.292 93.8302 94.2817 93.8737 94.2803C93.9601 94.2699 94.047 94.2939 94.1158 94.3472L99.5894 99.3501C99.6214 99.3792 99.6472 99.4144 99.6654 99.4537C99.6835 99.4929 99.6937 99.5354 99.6951 99.5786C99.6966 99.6219 99.6894 99.665 99.6739 99.7054C99.6585 99.7458 99.6351 99.7827 99.6052 99.8139L87.7445 112.787C87.6839 112.848 87.6031 112.884 87.5175 112.889C87.4771 112.894 87.4361 112.89 87.3976 112.877ZM82.5076 107.548L87.4698 112.094L98.9201 99.6383L93.966 95.0875L82.5076 107.548Z" fill="#002121" />
<path d="M90.5786 108.62L85.6982 104.144C85.0283 103.53 83.9874 103.575 83.3732 104.245L62.9753 126.494C62.3611 127.164 62.4062 128.205 63.076 128.819L67.9565 133.294C68.6263 133.909 69.6672 133.863 70.2814 133.193L90.6793 110.945C91.2935 110.275 91.2484 109.234 90.5786 108.62Z" fill="#03C7E8" />
<path d="M68.543 133.99C68.2434 133.908 67.9682 133.754 67.7412 133.542L62.8495 129.063C62.4689 128.709 62.2426 128.22 62.2195 127.7C62.1963 127.181 62.3781 126.673 62.7256 126.286L83.1283 104.037C83.4824 103.656 83.9719 103.43 84.4913 103.407C85.0107 103.384 85.5184 103.565 85.905 103.913L90.7904 108.39C91.171 108.744 91.3972 109.234 91.4204 109.753C91.4435 110.273 91.2618 110.78 90.9142 111.167L70.5243 133.42C70.2781 133.687 69.963 133.882 69.6136 133.983C69.2641 134.083 68.8938 134.086 68.543 133.99ZM84.937 104.091C84.8052 104.054 84.6678 104.039 84.5309 104.048C84.3574 104.056 84.1873 104.099 84.0304 104.173C83.8734 104.247 83.7325 104.352 83.616 104.48L63.2151 126.723C62.9827 126.981 62.8613 127.321 62.8771 127.668C62.8928 128.015 63.0444 128.341 63.2992 128.577L68.1845 133.054C68.4417 133.287 68.78 133.409 69.1265 133.395C69.473 133.38 69.7998 133.23 70.0366 132.976L90.434 110.746C90.6663 110.488 90.7878 110.149 90.772 109.802C90.7563 109.455 90.6046 109.128 90.3499 108.892L85.4645 104.415C85.3185 104.265 85.1372 104.154 84.937 104.091Z" fill="#002121" />
<path d="M119.367 71.6959C116.6 69.1548 113.141 67.492 109.428 66.9178C105.715 66.3436 101.916 66.8839 98.51 68.4703C95.1043 70.0567 92.2457 72.6179 90.296 75.8298C88.3463 79.0416 87.3932 82.7597 87.5572 86.5134C87.7212 90.2671 88.9951 93.8877 91.2175 96.9169C93.44 99.9461 96.5111 102.248 100.042 103.531C103.573 104.813 107.406 105.02 111.054 104.123C114.702 103.227 118.003 101.268 120.538 98.4946C123.931 94.7829 125.713 89.8768 125.494 84.8527C125.274 79.8287 123.071 75.097 119.367 71.6959ZM96.9839 96.0996C94.9233 94.2099 93.4694 91.7515 92.8059 89.0353C92.1424 86.3191 92.2992 83.467 93.2565 80.8399C94.2138 78.2127 95.9285 75.9283 98.1839 74.2757C100.439 72.6231 103.134 71.6765 105.927 71.5556C108.72 71.4346 111.487 72.1447 113.876 73.5962C116.266 75.0477 118.171 77.1752 119.352 79.7098C120.532 82.2445 120.934 85.0723 120.508 87.8357C120.081 90.5991 118.845 93.174 116.955 95.2348C114.421 97.9979 110.893 99.6412 107.148 99.8034C103.403 99.9656 99.7468 98.6333 96.9839 96.0996Z" fill="white" />
<path d="M101.371 104.313C96.6651 103.025 92.6175 100.01 90.0365 95.8695C87.4556 91.7285 86.5312 86.7663 87.4479 81.9735C88.3647 77.1806 91.055 72.9097 94.982 70.0134C98.9089 67.117 103.784 65.8084 108.633 66.3486C113.482 66.8888 117.949 69.2382 121.142 72.9277C124.335 76.6173 126.019 81.3755 125.858 86.2526C125.697 91.1296 123.703 95.7667 120.273 99.2381C116.844 102.709 112.232 104.76 107.358 104.98C105.34 105.074 103.319 104.848 101.371 104.313ZM111.529 67.7055C109.642 67.1867 107.687 66.9654 105.732 67.0496C101.063 67.2601 96.6449 69.2229 93.3586 72.5465C90.0724 75.8701 88.1594 80.3103 88.0012 84.9817C87.843 89.653 89.4512 94.2123 92.505 97.7502C95.5589 101.288 99.834 103.545 104.478 104.07C109.122 104.596 113.793 103.351 117.56 100.585C121.328 97.8185 123.914 93.7337 124.804 89.1451C125.694 84.5565 124.821 79.8012 122.361 75.8275C119.9 71.8538 116.033 68.9537 111.529 67.7055ZM102.661 99.6222C100.464 99.0148 98.4423 97.8945 96.7628 96.3534C94.1466 93.9593 92.5 90.6882 92.1352 87.1605C91.7704 83.6327 92.7128 80.0937 94.7836 77.2145C96.8544 74.3353 99.9096 72.3161 103.37 71.54C106.83 70.7638 110.455 71.2847 113.556 73.0037C116.658 74.7227 119.021 77.5203 120.197 80.8661C121.373 84.2118 121.281 87.873 119.937 91.1553C118.594 94.4375 116.093 97.1126 112.908 98.6733C109.724 100.234 106.077 100.572 102.661 99.6222ZM97.2188 95.8692C99.2303 97.7107 101.742 98.9152 104.437 99.3306C107.133 99.746 109.89 99.3537 112.363 98.2033C114.836 97.0529 116.912 95.1958 118.331 92.8664C119.75 90.5371 120.447 87.8397 120.334 85.1146C120.215 82.3875 119.291 79.7568 117.678 77.5551C116.064 75.3534 113.835 73.6794 111.27 72.7447C108.706 71.81 105.922 71.6565 103.27 72.3037C100.618 72.9509 98.2181 74.3696 96.3723 76.3807C93.9071 79.0806 92.612 82.6474 92.7707 86.3C92.9294 89.9527 94.5288 93.3936 97.2188 95.8692Z" fill="#002121" />
</svg>
);

export default ContentUnavailable;
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Factory.define('navigationBar')
user_message: null,
}))
.option('course_id', null, 'course-v1:edX+DemoX+Demo_Course')
.attr('is_enrolled', null, false)
.sequence('is_enrolled', ['isEnrolled'], (idx, isEnrolled) => isEnrolled)
.attr('is_self_paced', null, false)
.attr('is_staff', null, true)
.attr('number', null, 'DemoX')
Expand Down
4 changes: 2 additions & 2 deletions src/components/NavigationBar/data/api.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe('Navigation bar api tests', () => {
});

it('Successfully get navigation tabs', async () => {
axiosMock.onGet(`${getCourseMetadataApiUrl(courseId)}`).reply(200, (Factory.build('navigationBar', 1)));
axiosMock.onGet(`${getCourseMetadataApiUrl(courseId)}`).reply(200, (Factory.build('navigationBar', 1, { isEnrolled: true })));
await executeThunk(fetchTab(courseId, 'outline'), store.dispatch, store.getState);

expect(store.getState().courseTabs.tabs).toHaveLength(4);
Expand All @@ -58,7 +58,7 @@ describe('Navigation bar api tests', () => {
it('Denied to get navigation bar when user has no access on course', async () => {
axiosMock.onGet(`${getCourseMetadataApiUrl(courseId)}`).reply(
200,
(Factory.build('navigationBar', 1, { hasCourseAccess: false })),
(Factory.build('navigationBar', 1, { hasCourseAccess: false, isEnrolled: true })),
);
await executeThunk(fetchTab(courseId, 'outline'), store.dispatch, store.getState);

Expand Down
1 change: 1 addition & 0 deletions src/components/NavigationBar/data/slice.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const slice = createSlice({
courseTitle: payload.courseTitle,
courseNumber: payload.courseNumber,
org: payload.org,
isEnrolled: payload.isEnrolled,
}
),
},
Expand Down
1 change: 1 addition & 0 deletions src/components/NavigationBar/data/thunks.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export default function fetchTab(courseId, rootSlug) {
org: courseHomeCourseMetadata.org,
courseNumber: courseHomeCourseMetadata.number,
courseTitle: courseHomeCourseMetadata.title,
isEnrolled: courseHomeCourseMetadata.isEnrolled,
}));
}
} catch (e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React, { useCallback } from 'react';
import propTypes from 'prop-types';

import classNames from 'classnames';
import { useSelector } from 'react-redux';

import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Button } from '@edx/paragon';

import ContentUnavailableIcon from '../../assets/ContentUnavailable';
import selectCourseTabs from '../../components/NavigationBar/data/selectors';
import { useIsOnDesktop, useIsOnXLDesktop } from '../data/hooks';
import messages from '../messages';

const CourseContentUnavailable = ({ subTitleMessage }) => {
const intl = useIntl();
const isOnDesktop = useIsOnDesktop();
const isOnXLDesktop = useIsOnXLDesktop();
const { courseId } = useSelector(selectCourseTabs);

const redirectToDashboard = useCallback(() => {
window.location.replace(`${getConfig().LMS_BASE_URL}/courses/${courseId}/about`);
}, [courseId]);

return (
<div className="min-content-height justify-content-center align-items-center d-flex w-100 flex-column bg-white">
<div className={classNames('d-flex flex-column align-items-center', {
'content-unavailable-desktop': isOnDesktop || isOnXLDesktop,
'py-0 px-3': !isOnDesktop && !isOnXLDesktop,
})}
>
<ContentUnavailableIcon />
<h3 className="pt-3 font-weight-bold text-primary-500 text-center">{intl.formatMessage(messages.contentUnavailableTitle)}</h3>
<p className="pb-2 text-gray-500 text-center">{intl.formatMessage(subTitleMessage)}</p>
<Button onClick={redirectToDashboard} variant="outline-dark" className="font-size-14 py-2 px-2.5">
{intl.formatMessage(messages.contentUnavailableAction)}
</Button>
</div>
</div>
);
};

CourseContentUnavailable.propTypes = {
subTitleMessage: propTypes.shape({
id: propTypes.string,
defaultMessage: propTypes.string,
description: propTypes.string,
}).isRequired,
};

export default React.memo(CourseContentUnavailable);
123 changes: 75 additions & 48 deletions src/discussions/discussions-home/DiscussionsHome.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import { LearningHeader as Header } from '@edx/frontend-component-header';

import { Spinner } from '../../components';
import selectCourseTabs from '../../components/NavigationBar/data/selectors';
import { LOADED } from '../../components/NavigationBar/data/slice';
import { ALL_ROUTES, DiscussionProvider, Routes as ROUTES } from '../../data/constants';
import DiscussionContext from '../common/context';
import ContentUnavailable from '../course-content-unavailable/CourseContentUnavailable';
import {
useCourseDiscussionData, useIsOnDesktop, useRedirectToThread, useSidebarVisible,
} from '../data/hooks';
Expand Down Expand Up @@ -41,7 +43,9 @@ const DiscussionsHome = () => {
const postEditorVisible = useSelector(selectPostEditorVisible);
const provider = useSelector(selectDiscussionProvider);
const enableInContext = useSelector(selectEnableInContext);
const { courseNumber, courseTitle, org } = useSelector(selectCourseTabs);
const {
courseNumber, courseTitle, org, courseStatus, isEnrolled,
} = useSelector(selectCourseTabs);
const pageParams = useMatch(ROUTES.COMMENTS.PAGE)?.params;
const page = pageParams?.page || null;
const matchPattern = ALL_ROUTES.find((route) => matchPath({ path: route }, location.pathname));
Expand Down Expand Up @@ -80,6 +84,7 @@ const DiscussionsHome = () => {
)}
<main className="container-fluid d-flex flex-column p-0 w-100" id="main" tabIndex="-1">
{!enableInContextSidebar && <CourseTabsNavigation activeTab="discussion" courseId={courseId} />}
{(isEnrolled || enableInContextSidebar) && (
<div
className={classNames('header-action-bar bg-white position-sticky', {
'shadow-none border-light-300 border-bottom': enableInContextSidebar,
Expand All @@ -92,67 +97,89 @@ const DiscussionsHome = () => {
})}
>
{!enableInContextSidebar && (
<NavigationBar />
<NavigationBar />
)}
<PostActionsBar />
</div>
<DiscussionsRestrictionBanner />
</div>
)}

{provider === DiscussionProvider.LEGACY && (
<Suspense fallback={(<Spinner />)}>
<Routes>
{[
ROUTES.TOPICS.CATEGORY,
ROUTES.TOPICS.CATEGORY_POST,
ROUTES.TOPICS.CATEGORY_POST_EDIT,
ROUTES.TOPICS.TOPIC,
ROUTES.TOPICS.TOPIC_POST,
ROUTES.TOPICS.TOPIC_POST_EDIT,
].map((route) => (
<Route
key={route}
path={route}
element={<LegacyBreadcrumbMenu />}
/>
))}
</Routes>
</Suspense>
<Suspense fallback={(<Spinner />)}>
<Routes>
{[
ROUTES.TOPICS.CATEGORY,
ROUTES.TOPICS.CATEGORY_POST,
ROUTES.TOPICS.CATEGORY_POST_EDIT,
ROUTES.TOPICS.TOPIC,
ROUTES.TOPICS.TOPIC_POST,
ROUTES.TOPICS.TOPIC_POST_EDIT,
].map((route) => (
<Route
key={route}
path={route}
element={<LegacyBreadcrumbMenu />}
/>
))}
</Routes>
</Suspense>
)}
<div className="d-flex flex-row position-relative">
<Suspense fallback={(<Spinner />)}>
<DiscussionSidebar displaySidebar={displaySidebar} postActionBarRef={postActionBarRef} />
</Suspense>
{displayContentArea && (
{(courseStatus === LOADED || enableInContextSidebar) && (
<div>
{ isEnrolled === false ? (
<Suspense fallback={(<Spinner />)}>
<DiscussionContent />
</Suspense>
)}
{!displayContentArea && (
<Routes>
<>
{ROUTES.TOPICS.PATH.map(route => (
<Route
key={route}
path={`${route}/*`}
element={(enableInContext || enableInContextSidebar) ? <InContextEmptyTopics /> : <EmptyTopics />}
/>
))}
<Route
path={ROUTES.POSTS.MY_POSTS}
element={<EmptyPosts subTitleMessage={messages.emptyMyPosts} />}
/>
{[`${ROUTES.POSTS.PATH}/*`, ROUTES.POSTS.ALL_POSTS, ROUTES.LEARNERS.POSTS].map((route) => (
<Routes>
{ALL_ROUTES.map((route) => (
<Route
key={route}
path={route}
element={<EmptyPosts subTitleMessage={messages.emptyAllPosts} />}
element={(<ContentUnavailable subTitleMessage={messages.contentUnavailableSubTitle} />)}
/>
))}
<Route path={ROUTES.LEARNERS.PATH} element={<EmptyLearners />} />
</>
</Routes>
)}
</Routes>
</Suspense>
)
: (
<div className="d-flex flex-row position-relative">
<Suspense fallback={(<Spinner />)}>
<DiscussionSidebar displaySidebar={displaySidebar} postActionBarRef={postActionBarRef} />
</Suspense>
{displayContentArea && (
<Suspense fallback={(<Spinner />)}>
<DiscussionContent />
</Suspense>
)}
{!displayContentArea && (
<Routes>
<>
{ROUTES.TOPICS.PATH.map(route => (
<Route
key={route}
path={`${route}/*`}
element={(enableInContext || enableInContextSidebar)
? <InContextEmptyTopics /> : <EmptyTopics />}
/>
))}
<Route
path={ROUTES.POSTS.MY_POSTS}
element={<EmptyPosts subTitleMessage={messages.emptyMyPosts} />}
/>
{[`${ROUTES.POSTS.PATH}/*`, ROUTES.POSTS.ALL_POSTS, ROUTES.LEARNERS.POSTS].map((route) => (
<Route
key={route}
path={route}
element={<EmptyPosts subTitleMessage={messages.emptyAllPosts} />}
/>
))}
<Route path={ROUTES.LEARNERS.PATH} element={<EmptyLearners />} />
</>
</Routes>
)}
</div>
)}
</div>
)}
{!enableInContextSidebar && (
<DiscussionsProductTour />
)}
Expand Down
Loading

0 comments on commit ac17fd7

Please sign in to comment.