Compare commits
7 Commits
sundas/INF
...
saad/hacka
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa236f96d8 | ||
|
|
ac117c75de | ||
|
|
37ee085253 | ||
|
|
f13b34c6c7 | ||
|
|
1ba5b938c4 | ||
|
|
c1478dbb41 | ||
|
|
5cc5156b2b |
@@ -24,7 +24,7 @@ function HTMLLoader({
|
||||
|
||||
function typeset(code) {
|
||||
promise = promise.then(() => {
|
||||
if (typeof window?.MathJax !== 'undefined') {
|
||||
if (typeof window?.MathJax !== 'undefined' && typeof window?.MathJax.startup !== 'undefined') {
|
||||
window.MathJax.startup.defaultPageReady().then((window.MathJax?.typesetPromise(code())));
|
||||
}
|
||||
return null;
|
||||
|
||||
1
src/components/NavigationBar/data/__factories__/index.js
Normal file
1
src/components/NavigationBar/data/__factories__/index.js
Normal file
@@ -0,0 +1 @@
|
||||
import './navigationBar.factory';
|
||||
@@ -0,0 +1,50 @@
|
||||
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`,
|
||||
}]);
|
||||
@@ -4,6 +4,8 @@ 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 {
|
||||
@@ -21,7 +23,7 @@ function normalizeCourseHomeCourseMetadata(metadata, rootSlug) {
|
||||
}
|
||||
|
||||
export async function getCourseHomeCourseMetadata(courseId, rootSlug) {
|
||||
const url = `${getApiBaseUrl()}/api/course_home/course_metadata/${courseId}`;
|
||||
const url = getCourseMetadataApiUrl(courseId);
|
||||
// don't know the context of adding timezone in url. hence omitting it
|
||||
// url = appendBrowserTimezoneToUrl(url);
|
||||
const { data } = await getAuthenticatedHttpClient().get(url);
|
||||
|
||||
65
src/components/NavigationBar/data/api.test.js
Normal file
65
src/components/NavigationBar/data/api.test.js
Normal file
@@ -0,0 +1,65 @@
|
||||
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');
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
/* 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,
|
||||
@@ -26,7 +27,11 @@ export function fetchTab(courseId, rootSlug) {
|
||||
}));
|
||||
}
|
||||
} catch (e) {
|
||||
dispatch(fetchTabFailure({ courseId }));
|
||||
if (getHttpErrorStatus(e) === 403) {
|
||||
dispatch(fetchTabDenied({ courseId }));
|
||||
} else {
|
||||
dispatch(fetchTabFailure({ courseId }));
|
||||
}
|
||||
logError(e);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -55,6 +55,8 @@ export const ContentActions = {
|
||||
CHANGE_TOPIC: 'topic_id',
|
||||
CHANGE_TYPE: 'type',
|
||||
VOTE: 'voted',
|
||||
ACCEPT_REVIEW: 'accept_review',
|
||||
REJECT_REVIEW: 'reject_review',
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -75,7 +75,7 @@ function ActionsDropdown({
|
||||
placement="bottom-end"
|
||||
>
|
||||
<div
|
||||
className="bg-white p-1 shadow d-flex flex-column"
|
||||
className="bg-white shadow d-flex flex-column"
|
||||
data-testid="actions-dropdown-modal-popup"
|
||||
>
|
||||
{actions.map(action => (
|
||||
@@ -91,9 +91,15 @@ function ActionsDropdown({
|
||||
close();
|
||||
handleActions(action.action);
|
||||
}}
|
||||
className="d-flex justify-content-start py-1.5 mr-4"
|
||||
className="d-flex justify-content-start actions-dropdown-item"
|
||||
>
|
||||
<Icon src={action.icon} className="mr-1" /> {intl.formatMessage(action.label)}
|
||||
<Icon
|
||||
src={action.icon}
|
||||
className="icon-size-24"
|
||||
/>
|
||||
<span className="font-weight-normal font-xl ml-2">
|
||||
{intl.formatMessage(action.label)}
|
||||
</span>
|
||||
</Dropdown.Item>
|
||||
</React.Fragment>
|
||||
))}
|
||||
|
||||
@@ -12,6 +12,8 @@ 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';
|
||||
@@ -28,6 +30,7 @@ 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;
|
||||
@@ -247,4 +250,12 @@ 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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,7 +14,3 @@ 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
|
||||
);
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
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',
|
||||
|
||||
@@ -26,6 +26,16 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Unpin',
|
||||
description: 'Action to unpin a post',
|
||||
},
|
||||
acceptReview: {
|
||||
id: 'discussions.actions.reviewAccept',
|
||||
defaultMessage: 'Accept',
|
||||
description: 'Action to accept content flagged for review',
|
||||
},
|
||||
rejectReview: {
|
||||
id: 'discussions.actions.reviewReject',
|
||||
defaultMessage: 'Decline',
|
||||
description: 'Action to reject content flagged for review',
|
||||
},
|
||||
deleteAction: {
|
||||
id: 'discussions.actions.delete',
|
||||
defaultMessage: 'Delete',
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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';
|
||||
@@ -9,13 +8,11 @@ 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);
|
||||
|
||||
@@ -28,7 +25,6 @@ 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',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import * as timeago from 'timeago.js';
|
||||
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
@@ -14,7 +14,6 @@ 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';
|
||||
@@ -60,7 +59,6 @@ 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);
|
||||
|
||||
@@ -101,7 +99,6 @@ 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',
|
||||
|
||||
@@ -151,6 +151,7 @@ export async function updateThread(threadId, {
|
||||
pinned,
|
||||
editReasonCode,
|
||||
closeReasonCode,
|
||||
reviewStatus,
|
||||
} = {}) {
|
||||
const url = `${getThreadsApiUrl()}${threadId}/`;
|
||||
const patchData = snakeCaseObject({
|
||||
@@ -166,6 +167,7 @@ export async function updateThread(threadId, {
|
||||
pinned,
|
||||
editReasonCode,
|
||||
closeReasonCode,
|
||||
reviewStatus,
|
||||
});
|
||||
const { data } = await getAuthenticatedHttpClient()
|
||||
.patch(url, patchData, { headers: { 'Content-Type': 'application/merge-patch+json' } });
|
||||
|
||||
@@ -44,7 +44,3 @@ 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
|
||||
);
|
||||
|
||||
@@ -239,6 +239,7 @@ export function createNewThread({
|
||||
|
||||
export function updateExistingThread(threadId, {
|
||||
flagged, voted, read, topicId, type, title, content, following, closed, pinned, closeReasonCode, editReasonCode,
|
||||
reviewStatus,
|
||||
}) {
|
||||
return async (dispatch) => {
|
||||
try {
|
||||
@@ -256,6 +257,7 @@ export function updateExistingThread(threadId, {
|
||||
pinned,
|
||||
editReasonCode,
|
||||
closeReasonCode,
|
||||
reviewStatus,
|
||||
}));
|
||||
const data = await updateThread(threadId, {
|
||||
flagged,
|
||||
@@ -270,6 +272,7 @@ export function updateExistingThread(threadId, {
|
||||
pinned,
|
||||
editReasonCode,
|
||||
closeReasonCode,
|
||||
reviewStatus,
|
||||
});
|
||||
dispatch(updateThreadSuccess(camelCaseObject(data)));
|
||||
} catch (error) {
|
||||
|
||||
@@ -44,6 +44,7 @@ function Post({
|
||||
const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
|
||||
const displayPostFooter = post.following || post.voteCount || post.closed
|
||||
|| (post.groupId && userHasModerationPrivileges);
|
||||
const displayReviewContentLabel = post.reviewStatus === "PENDING";
|
||||
|
||||
const handleAbusedFlag = useCallback(() => {
|
||||
if (post.abuseFlagged) {
|
||||
@@ -85,6 +86,8 @@ function Post({
|
||||
[ContentActions.COPY_LINK]: () => { navigator.clipboard.writeText(`${window.location.origin}/${courseId}/posts/${post.id}`); },
|
||||
[ContentActions.PIN]: () => dispatch(updateExistingThread(post.id, { pinned: !post.pinned })),
|
||||
[ContentActions.REPORT]: () => handleAbusedFlag(),
|
||||
[ContentActions.ACCEPT_REVIEW]: () => dispatch(updateExistingThread(post.id, { reviewStatus: "ACCEPTED" })),
|
||||
[ContentActions.REJECT_REVIEW]: () => dispatch(updateExistingThread(post.id, { reviewStatus: "REJECTED" })),
|
||||
}), [
|
||||
showDeleteConfirmation,
|
||||
history,
|
||||
@@ -144,6 +147,8 @@ function Post({
|
||||
/>
|
||||
<AlertBanner content={post} />
|
||||
<PostHeader post={post} />
|
||||
{displayReviewContentLabel && <p style= {{ background: "yellow" }}> This content is under review </p>}
|
||||
|
||||
<div className="d-flex mt-14px text-break font-style text-primary-500">
|
||||
<HTMLLoader htmlNode={post.renderedBody} componentId="post" cssClassName="html-loader" testId={post.id} />
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,6 @@ 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';
|
||||
@@ -11,14 +10,12 @@ 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(() => {
|
||||
@@ -63,7 +60,6 @@ export function PostAvatar({
|
||||
width: avatarSize,
|
||||
}}
|
||||
alt={post.author}
|
||||
src={authorAvatars?.imageUrlSmall}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,7 +5,8 @@ import { generatePath, useRouteMatch } from 'react-router';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import {
|
||||
CheckCircle, CheckCircleOutline, Delete, Edit, Lock, LockOpen, Pin, Report, Verified, VerifiedOutline,
|
||||
CheckCircle, CheckCircleOutline, Close, Delete, Done,
|
||||
Edit, Lock, LockOpen, Pin, Report, Verified, VerifiedOutline,
|
||||
} from '@edx/paragon/icons';
|
||||
|
||||
import { InsertLink } from '../components/icons';
|
||||
@@ -173,6 +174,20 @@ export const ACTIONS_LIST = [
|
||||
label: messages.deleteAction,
|
||||
conditions: { canDelete: true },
|
||||
},
|
||||
{
|
||||
id: 'accept-review',
|
||||
action: ContentActions.ACCEPT_REVIEW,
|
||||
icon: Done,
|
||||
label: messages.acceptReview,
|
||||
conditions: { reviewStatus: 'PENDING' },
|
||||
},
|
||||
{
|
||||
id: 'reject-review',
|
||||
action: ContentActions.REJECT_REVIEW,
|
||||
icon: Close,
|
||||
label: messages.rejectReview,
|
||||
conditions: { reviewStatus: 'PENDING' },
|
||||
},
|
||||
];
|
||||
|
||||
export function useActions(content) {
|
||||
|
||||
@@ -502,3 +502,19 @@ 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user