Compare commits

..

1 Commits

Author SHA1 Message Date
ayesha waris
0efe463a63 fix: changes empty state image and text to monochrome 2023-04-03 11:57:07 +05:00
15 changed files with 31 additions and 165 deletions

View File

@@ -24,9 +24,7 @@ function HTMLLoader({
function typeset(code) {
promise = promise.then(() => {
if (typeof window?.MathJax !== 'undefined' && typeof window?.MathJax.startup !== 'undefined') {
window.MathJax.startup.defaultPageReady().then((window.MathJax?.typesetPromise(code())));
}
if (typeof window?.MathJax !== 'undefined') { return window.MathJax?.typesetPromise(code()); }
return null;
})
.catch((err) => logError(`Typeset failed: ${err.message}`));

View File

@@ -1 +0,0 @@
import './navigationBar.factory';

View File

@@ -1,50 +0,0 @@
import { Factory } from 'rosie';
import { getApiBaseUrl } from '../../../../data/constants';
Factory.define('navigationBar')
.attr('can_show_upgrade_sock', null, false)
.attr('can_view_certificate', null, false)
.attr('celebrations', null, {
first_section: false, streak_discount_enabled: false, streak_length_to_celebrate: null, weekly_goal: false,
})
.option('hasCourseAccess', null, true)
.attr('course_access', ['hasCourseAccess'], (hasCourseAccess) => ({
additional_context_user_message: null,
developer_message: null,
error_code: null,
has_access: hasCourseAccess,
user_fragment: null,
user_message: null,
}))
.option('course_id', null, 'course-v1:edX+DemoX+Demo_Course')
.attr('is_enrolled', null, false)
.attr('is_self_paced', null, false)
.attr('is_staff', null, true)
.attr('number', null, 'DemoX')
.attr('org', null, 'edX')
.attr('original_user_is_staff', null, true)
.attr('title', null, 'Demonstration Course')
.attr('username', null, 'edx')
.attr('tabs', ['course_id'], (idx, courseId) => [
{
tab_id: 'courseware',
title: 'Course',
url: `${getApiBaseUrl}/course/${courseId}/home`,
},
{
tab_id: 'progress',
title: 'Progress',
url: `${getApiBaseUrl}/course/${courseId}/progress`,
},
{
tab_id: 'discussion',
title: 'Discussion',
url: `${getApiBaseUrl}/course/${courseId}/discussion/forum/`,
},
{
tab_id: 'instructor',
title: 'Instructor',
url: `${getApiBaseUrl}/course/${courseId}/instructor`,
}]);

View File

@@ -4,8 +4,6 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { getApiBaseUrl } from '../../../data/constants';
export const getCourseMetadataApiUrl = (courseId) => `${getApiBaseUrl()}/api/course_home/course_metadata/${courseId}`;
function normalizeCourseHomeCourseMetadata(metadata, rootSlug) {
const data = camelCaseObject(metadata);
return {
@@ -23,7 +21,7 @@ function normalizeCourseHomeCourseMetadata(metadata, rootSlug) {
}
export async function getCourseHomeCourseMetadata(courseId, rootSlug) {
const url = getCourseMetadataApiUrl(courseId);
const url = `${getApiBaseUrl()}/api/course_home/course_metadata/${courseId}`;
// don't know the context of adding timezone in url. hence omitting it
// url = appendBrowserTimezoneToUrl(url);
const { data } = await getAuthenticatedHttpClient().get(url);

View File

@@ -1,65 +0,0 @@
import MockAdapter from 'axios-mock-adapter';
import { Factory } from 'rosie';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { initializeMockApp } from '@edx/frontend-platform/testing';
import { initializeStore } from '../../../store';
import { executeThunk } from '../../../test-utils';
import { getCourseMetadataApiUrl } from './api';
import { fetchTab } from './thunks';
import './__factories__';
const courseId = 'course-v1:edX+TestX+Test_Course';
let axiosMock = null;
let store;
describe('Navigation bar api tests', () => {
beforeEach(() => {
initializeMockApp({
authenticatedUser: {
userId: 3,
username: 'abc123',
administrator: true,
roles: [],
},
});
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
store = initializeStore();
});
afterEach(() => {
axiosMock.reset();
});
it('Successfully get navigation tabs', async () => {
axiosMock.onGet(`${getCourseMetadataApiUrl(courseId)}`).reply(200, (Factory.build('navigationBar', 1)));
await executeThunk(fetchTab(courseId, 'outline'), store.dispatch, store.getState);
expect(store.getState().courseTabs.tabs).toHaveLength(4);
expect(store.getState().courseTabs.courseStatus).toEqual('loaded');
});
it('Failed to get navigation tabs', async () => {
axiosMock.onGet(`${getCourseMetadataApiUrl(courseId)}`).reply(404);
await executeThunk(fetchTab(courseId, 'outline'), store.dispatch, store.getState);
expect(store.getState().courseTabs.courseStatus).toEqual('failed');
});
it('Denied to get navigation tabs', async () => {
axiosMock.onGet(`${getCourseMetadataApiUrl(courseId)}`).reply(403, {});
await executeThunk(fetchTab(courseId, 'outline'), store.dispatch, store.getState);
expect(store.getState().courseTabs.courseStatus).toEqual('denied');
});
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 })));
await executeThunk(fetchTab(courseId, 'outline'), store.dispatch, store.getState);
expect(store.getState().courseTabs.courseStatus).toEqual('denied');
});
});

View File

@@ -1,7 +1,6 @@
/* eslint-disable import/prefer-default-export, no-unused-expressions */
import { logError } from '@edx/frontend-platform/logging';
import { getHttpErrorStatus } from '../../../discussions/utils';
import { getCourseHomeCourseMetadata } from './api';
import {
fetchTabDenied,
@@ -27,11 +26,7 @@ export function fetchTab(courseId, rootSlug) {
}));
}
} catch (e) {
if (getHttpErrorStatus(e) === 403) {
dispatch(fetchTabDenied({ courseId }));
} else {
dispatch(fetchTabFailure({ courseId }));
}
dispatch(fetchTabFailure({ courseId }));
logError(e);
}
};

View File

@@ -75,7 +75,7 @@ function ActionsDropdown({
placement="bottom-end"
>
<div
className="bg-white shadow d-flex flex-column"
className="bg-white p-1 shadow d-flex flex-column"
data-testid="actions-dropdown-modal-popup"
>
{actions.map(action => (
@@ -91,15 +91,9 @@ function ActionsDropdown({
close();
handleActions(action.action);
}}
className="d-flex justify-content-start actions-dropdown-item"
className="d-flex justify-content-start py-1.5 mr-4"
>
<Icon
src={action.icon}
className="icon-size-24"
/>
<span className="font-weight-normal font-xl ml-2">
{intl.formatMessage(action.label)}
</span>
<Icon src={action.icon} className="mr-1" /> {intl.formatMessage(action.label)}
</Dropdown.Item>
</React.Fragment>
))}

View File

@@ -12,8 +12,6 @@ import { initializeMockApp } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { AppProvider } from '@edx/frontend-platform/react';
import { getCourseMetadataApiUrl } from '../../components/NavigationBar/data/api';
import { fetchTab } from '../../components/NavigationBar/data/thunks';
import { getApiBaseUrl } from '../../data/constants';
import { initializeStore } from '../../store';
import { executeThunk } from '../../test-utils';
@@ -30,7 +28,6 @@ import DiscussionsHome from './DiscussionsHome';
import '../posts/data/__factories__/threads.factory';
import '../in-context-topics/data/__factories__/inContextTopics.factory';
import '../topics/data/__factories__/topics.factory';
import '../../components/NavigationBar/data/__factories__/navigationBar.factory';
const courseConfigApiUrl = getCourseConfigApiUrl();
let axiosMock;
@@ -250,12 +247,4 @@ describe('DiscussionsHome', () => {
expect(screen.queryByText('No topic selected')).toBeInTheDocument();
});
it('should display navigation tabs', async () => {
axiosMock.onGet(`${getCourseMetadataApiUrl(courseId)}`).reply(200, (Factory.build('navigationBar', 1)));
await executeThunk(fetchTab(courseId, 'outline'), store.dispatch, store.getState);
renderComponent(`/${courseId}/topics`);
expect(screen.queryByText('Discussion')).toBeInTheDocument();
});
});

View File

@@ -14,3 +14,7 @@ export const selectUsernameSearch = () => state => state.learners.usernameSearch
export const selectLearnerSorting = () => state => state.learners.sortedBy;
export const selectLearnerNextPage = () => state => state.learners.nextPage;
export const selectLearnerAvatar = author => state => (
state.learners.learnerProfiles[author]?.profileImage?.imageUrlLarge
);

View File

@@ -1,15 +1,20 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { Avatar } from '@edx/paragon';
import { selectLearnerAvatar } from '../data/selectors';
import { learnerShape } from './proptypes';
function LearnerAvatar({ learner }) {
const learnerAvatar = useSelector(selectLearnerAvatar(learner.username));
return (
<div className="mr-3 mt-1">
<Avatar
size="sm"
alt={learner.username}
src={learnerAvatar}
style={{
height: '2rem',
width: '2rem',

View File

@@ -1,6 +1,7 @@
import React from 'react';
import classNames from 'classnames';
import { useSelector } from 'react-redux';
import { injectIntl } from '@edx/frontend-platform/i18n';
import { Avatar } from '@edx/paragon';
@@ -8,11 +9,13 @@ import { Avatar } from '@edx/paragon';
import { AvatarOutlineAndLabelColors } from '../../../../data/constants';
import { AuthorLabel } from '../../../common';
import { useAlertBannerVisible } from '../../../data/hooks';
import { selectAuthorAvatars } from '../../../posts/data/selectors';
import { commentShape } from './proptypes';
function CommentHeader({
comment,
}) {
const authorAvatars = useSelector(selectAuthorAvatars(comment.author));
const colorClass = AvatarOutlineAndLabelColors[comment.authorLabel];
const hasAnyAlert = useAlertBannerVisible(comment);
@@ -25,6 +28,7 @@ function CommentHeader({
<Avatar
className={`border-0 ml-0.5 mr-2.5 ${colorClass ? `outline-${colorClass}` : 'outline-anonymous'}`}
alt={comment.author}
src={authorAvatars?.imageUrlSmall}
style={{
width: '32px',
height: '32px',

View File

@@ -1,7 +1,7 @@
import React, { useCallback, useMemo, useState } from 'react';
import PropTypes from 'prop-types';
import { useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import * as timeago from 'timeago.js';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
@@ -14,6 +14,7 @@ import {
} from '../../../common';
import timeLocale from '../../../common/time-locale';
import { useAlertBannerVisible } from '../../../data/hooks';
import { selectAuthorAvatars } from '../../../posts/data/selectors';
import { editComment, removeComment } from '../../data/thunks';
import messages from '../../messages';
import CommentEditor from './CommentEditor';
@@ -59,6 +60,7 @@ function Reply({
[ContentActions.REPORT]: () => handleAbusedFlag(),
}), [dispatch, handleAbusedFlag, reply.endorsed, reply.id, showDeleteConfirmation]);
const authorAvatars = useSelector(selectAuthorAvatars(reply.author));
const colorClass = AvatarOutlineAndLabelColors[reply.authorLabel];
const hasAnyAlert = useAlertBannerVisible(reply);
@@ -99,6 +101,7 @@ function Reply({
<Avatar
className={`ml-0.5 mt-0.5 border-0 ${colorClass ? `outline-${colorClass}` : 'outline-anonymous'}`}
alt={reply.author}
src={authorAvatars?.imageUrlSmall}
style={{
width: '32px',
height: '32px',

View File

@@ -44,3 +44,7 @@ export const selectThreadSorting = () => state => state.threads.sortedBy;
export const selectThreadFilters = () => state => state.threads.filters;
export const selectThreadNextPage = () => state => state.threads.nextPage;
export const selectAuthorAvatars = author => state => (
state.threads.avatars?.[author]?.profile.image
);

View File

@@ -2,6 +2,7 @@ import React, { useMemo } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { useSelector } from 'react-redux';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Avatar, Badge, Icon } from '@edx/paragon';
@@ -10,12 +11,14 @@ import { Issue, Question } from '../../../components/icons';
import { AvatarOutlineAndLabelColors, ThreadType } from '../../../data/constants';
import { AuthorLabel } from '../../common';
import { useAlertBannerVisible } from '../../data/hooks';
import { selectAuthorAvatars } from '../data/selectors';
import messages from './messages';
import { postShape } from './proptypes';
export function PostAvatar({
post, authorLabel, fromPostLink, read,
}) {
const authorAvatars = useSelector(selectAuthorAvatars(post.author));
const outlineColor = AvatarOutlineAndLabelColors[authorLabel];
const avatarSize = useMemo(() => {
@@ -60,6 +63,7 @@ export function PostAvatar({
width: avatarSize,
}}
alt={post.author}
src={authorAvatars?.imageUrlSmall}
/>
</div>
);

View File

@@ -502,19 +502,3 @@ header {
padding-left: 1rem;
}
}
.icon-size-24 {
width: 1.5rem;
height: 1.5rem;
}
.actions-dropdown-item {
padding: 12px 16px;
height: 48px;
width: 304px;
}
.font-xl {
font-size: 18px;
line-height: 28px;
}