Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions src/assets/undelete.svg

This file was deleted.

45 changes: 1 addition & 44 deletions src/components/FilterBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,6 @@ const FilterBar = ({
label: intl.formatMessage(messages.filterUnresponded),
value: PostsStatusFilter.UNRESPONDED,
},
{
id: 'status-active',
label: intl.formatMessage(messages.filterActive),
value: PostsStatusFilter.ACTIVE,
},
{
id: 'status-deleted',
label: intl.formatMessage(messages.filterDeleted),
value: PostsStatusFilter.DELETED,
},
{
id: 'sort-activity',
label: intl.formatMessage(messages.lastActivityAt),
Expand Down Expand Up @@ -134,7 +124,7 @@ const FilterBar = ({
<Collapsible.Body className="collapsible-body px-4 pb-3 pt-0">
<Form>
<div className="d-flex flex-row py-2 justify-content-between">
{filters.filter(f => !f.hasSeparator).map((value) => (
{filters.map((value) => (
<Form.RadioSet
key={value.name}
name={value.name}
Expand All @@ -160,38 +150,6 @@ const FilterBar = ({
</Form.RadioSet>
))}
</div>
{filters.some(f => f.hasSeparator) && (
<>
<div className="border-bottom my-2" />
<div className="d-flex flex-row py-2 justify-content-between">
{filters.filter(f => f.hasSeparator).map((value) => (
<Form.RadioSet
key={value.name}
name={value.name}
className="d-flex flex-column list-group list-group-flush"
value={selectedFilters[value.name]}
onChange={handleFilterToggle}
>
{value.filters.map(filterName => {
const element = allFilters.find(obj => obj.id === filterName);
if (element) {
return (
<ActionItem
key={element.id}
id={element.id}
label={element.label}
value={element.value}
selected={selectedFilters[value.name]}
/>
);
}
return false;
})}
</Form.RadioSet>
))}
</div>
</>
)}
{showCohortsFilter && (
<>
<div className="border-bottom my-2" />
Expand Down Expand Up @@ -241,7 +199,6 @@ FilterBar.propTypes = {
selectedFilters: PropTypes.shape({
postType: ThreadType,
status: PostsStatusFilter,
contentStatus: PostsStatusFilter,
orderBy: ThreadOrdering,
cohort: PropTypes.string,
}).isRequired,
Expand Down
6 changes: 0 additions & 6 deletions src/data/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ export const ContentActions = {
COPY_LINK: 'copy_link',
REPORT: 'abuse_flagged',
DELETE: 'delete',
RESTORE: 'restore',
FOLLOWING: 'following',
CHANGE_GROUP: 'group_id',
MARK_READ: 'read',
Expand All @@ -61,8 +60,6 @@ export const ContentActions = {
VOTE: 'voted',
DELETE_COURSE_POSTS: 'delete-course-posts',
DELETE_ORG_POSTS: 'delete-org-posts',
RESTORE_COURSE_POSTS: 'restore-course-posts',
RESTORE_ORG_POSTS: 'restore-org-posts',
};

/**
Expand Down Expand Up @@ -112,8 +109,6 @@ export const PostsStatusFilter = {
REPORTED: 'statusReported',
UNANSWERED: 'statusUnanswered',
UNRESPONDED: 'statusUnresponded',
ACTIVE: 'statusActive',
DELETED: 'statusDeleted',
};

/**
Expand All @@ -137,7 +132,6 @@ export const LearnersOrdering = {
BY_FLAG: 'flagged',
BY_LAST_ACTIVITY: 'activity',
BY_RECENCY: 'recency',
BY_DELETED: 'deleted',
};

/**
Expand Down
5 changes: 1 addition & 4 deletions src/discussions/common/ActionsDropdown.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,10 @@ const ActionsDropdown = ({
size="inline"
onClick={() => {
close();
if (!action.disabled) {
handleActions(action.action);
}
handleActions(action.action);
}}
className="d-flex justify-content-start actions-dropdown-item"
data-testId={action.id}
disabled={action.disabled}
>
<Icon
src={action.icon}
Expand Down
16 changes: 4 additions & 12 deletions src/discussions/common/HoverCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ const HoverCard = ({
voted,
following,
endorseIcons,
isDeleted,
}) => {
const intl = useIntl();
const { enableInContextSidebar } = useContext(DiscussionContext);
Expand All @@ -51,9 +50,9 @@ const HoverCard = ({
'px-2.5 py-2 border-0 font-style text-gray-700',
{ 'w-100': enableInContextSidebar },
)}
onClick={handleResponseCommentButton}
disabled={isClosed || isDeleted}
style={{ lineHeight: '20px', ...(isDeleted ? { opacity: 0.3, cursor: 'not-allowed' } : {}) }}
onClick={() => handleResponseCommentButton()}
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The button's onClick handler is unnecessarily wrapped in an arrow function. The current code onClick={() => handleResponseCommentButton()} should be simplified to onClick={handleResponseCommentButton} for better performance and cleaner code, unless parameters need to be passed.

Suggested change
onClick={() => handleResponseCommentButton()}
onClick={handleResponseCommentButton}

Copilot uses AI. Check for mistakes.
disabled={isClosed}
style={{ lineHeight: '20px' }}
>
{addResponseCommentButtonMessage}
</Button>
Expand All @@ -79,8 +78,6 @@ const HoverCard = ({
className={['endorse', 'unendorse'].includes(endorseIcons.id) ? 'text-dark-500' : 'text-success-500'}
size="sm"
alt="Endorse"
disabled={isDeleted}
style={isDeleted ? { opacity: 0.3, cursor: 'not-allowed' } : {}}
/>
</OverlayTrigger>
</div>
Expand All @@ -98,9 +95,8 @@ const HoverCard = ({
iconAs={Icon}
size="sm"
alt="Like"
disabled={!userHasLikePermission || isDeleted}
disabled={!userHasLikePermission}
iconClassNames="like-icon-dimensions"
style={isDeleted ? { opacity: 0.3, cursor: 'not-allowed' } : {}}
onClick={(e) => {
e.preventDefault();
onLike();
Expand All @@ -123,8 +119,6 @@ const HoverCard = ({
size="sm"
alt="Follow"
iconClassNames="follow-icon-dimensions"
disabled={isDeleted}
style={isDeleted ? { opacity: 0.3, cursor: 'not-allowed' } : {}}
onClick={(e) => {
e.preventDefault();
onFollow();
Expand Down Expand Up @@ -171,14 +165,12 @@ HoverCard.propTypes = {
)),
onFollow: PropTypes.func,
following: PropTypes.bool,
isDeleted: PropTypes.bool,
};

HoverCard.defaultProps = {
onFollow: () => null,
endorseIcons: null,
following: undefined,
isDeleted: false,
};

export default React.memo(HoverCard);
5 changes: 0 additions & 5 deletions src/discussions/data/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,3 @@ export const ContentTypes = {
POST: 'POST',
COMMENT: 'COMMENT',
};

export const THREAD_FILTER_TYPES = {
ACTIVE: 'active',
DELETED: 'deleted',
};
2 changes: 1 addition & 1 deletion src/discussions/data/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export function selectAreThreadsFiltered(state) {
}

return !(
(filters.status === PostsStatusFilter.ALL || filters.status === PostsStatusFilter.ACTIVE)
filters.status === PostsStatusFilter.ALL
&& filters.postType === ThreadType.ALL
);
}
Expand Down
114 changes: 38 additions & 76 deletions src/discussions/learners/LearnerActionsDropdown.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import React, {
useCallback, useEffect, useRef, useState,
useCallback, useRef, useState,
} from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';

import {
Button, Dropdown, Icon, IconButton, ModalPopup, useToggle,
} from '@openedx/paragon';
import { ChevronRight, MoreHoriz } from '@openedx/paragon/icons';
import { MoreHoriz } from '@openedx/paragon/icons';

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

import { useLearnerActionsMenu } from './utils';
import { useLearnerActions } from './utils';

const LearnerActionsDropdown = ({
actionHandlers,
Expand All @@ -22,16 +21,14 @@ const LearnerActionsDropdown = ({
const intl = useIntl();
const [isOpen, open, close] = useToggle(false);
const [target, setTarget] = useState(null);
const [activeSubmenu, setActiveSubmenu] = useState(null);
const menuItems = useLearnerActionsMenu(intl, userHasBulkDeletePrivileges);
const actions = useLearnerActions(userHasBulkDeletePrivileges);

const handleActions = useCallback((action) => {
const actionFunction = actionHandlers[action];
if (actionFunction) {
actionFunction();
close();
}
}, [actionHandlers, close]);
}, [actionHandlers]);
Comment on lines 26 to +31
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The handleActions callback is missing close in its dependency array. While close is called in the onClick handler on line 73, it should be included in the handleActions dependencies for consistency, or the close() call should be moved to the onClick handler instead of inside handleActions to avoid confusion.

Copilot uses AI. Check for mistakes.

const onClickButton = useCallback((event) => {
event.preventDefault();
Expand All @@ -42,15 +39,8 @@ const LearnerActionsDropdown = ({
const onCloseModal = useCallback(() => {
close();
setTarget(null);
setActiveSubmenu(null);
}, [close]);

// Cleanup portal on unmount to prevent memory leaks and orphaned DOM nodes
useEffect(() => () => {
setTarget(null);
setActiveSubmenu(null);
}, []);

return (
<>
<IconButton
Expand All @@ -63,69 +53,41 @@ const LearnerActionsDropdown = ({
iconClassNames={dropDownIconSize ? 'dropdown-icon-dimensions' : ''}
/>
<div className="actions-dropdown">
{isOpen && ReactDOM.createPortal(
<ModalPopup
onClose={onCloseModal}
positionRef={target}
isOpen={isOpen}
placement="bottom-start"
style={{ zIndex: 9998 }}
<ModalPopup
onClose={onCloseModal}
positionRef={target}
isOpen={isOpen}
placement="bottom-start"
>
<div
className="bg-white shadow d-flex flex-column mt-1"
data-testid="learner-actions-dropdown-modal-popup"
>
<div
className="bg-white shadow d-flex flex-column mt-1"
data-testid="learner-actions-dropdown-modal-popup"
style={{ position: 'relative', zIndex: 9998 }}
>
{menuItems.map(item => (
<div
key={item.id}
className="position-relative"
onMouseEnter={() => setActiveSubmenu(item.id)}
onMouseLeave={() => setActiveSubmenu(null)}
style={{ zIndex: 2 }}
{actions.map(action => (
<React.Fragment key={action.id}>
<Dropdown.Item
as={Button}
variant="tertiary"
size="inline"
onClick={() => {
close();
handleActions(action.action);
}}
className="d-flex justify-content-start actions-dropdown-item"
data-testId={action.id}
>
<Dropdown.Item
as={Button}
variant="tertiary"
size="inline"
className="d-flex justify-content-between align-items-center actions-dropdown-item"
data-testid={item.id}
>
<div className="d-flex align-items-center">
<span className="font-weight-normal">
{item.label}
</span>
</div>
<Icon
src={ChevronRight}
className="icon-size-16"
/>
</Dropdown.Item>
{activeSubmenu === item.id && (
<div className="bg-white learner-submenu-container">
{item.submenu.map(subItem => (
<Dropdown.Item
key={subItem.id}
as={Button}
variant="tertiary"
size="inline"
onClick={() => handleActions(subItem.action)}
className="d-flex justify-content-start actions-dropdown-item"
data-testid={subItem.id}
>
<span className="font-weight-normal">
{subItem.label}
</span>
</Dropdown.Item>
))}
</div>
)}
</div>
))}
</div>
</ModalPopup>,
document.body,
)}
<Icon
src={action.icon}
className="icon-size-24"
/>
<span className="font-weight-normal ml-2">
{action.label.defaultMessage}
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The message formatting with intl.formatMessage should be used here instead of directly accessing action.label.defaultMessage. This bypasses the internationalization system and will not respect the user's language preferences or locale settings.

Suggested change
{action.label.defaultMessage}
{intl.formatMessage(action.label)}

Copilot uses AI. Check for mistakes.
</span>
</Dropdown.Item>
</React.Fragment>
))}
</div>
</ModalPopup>
</div>
</>
);
Expand Down
Loading