From b1e317427d6686fdce38dff87ae1fc3f2cb656f3 Mon Sep 17 00:00:00 2001 From: Kyrylo Hudym-Levkovych Date: Wed, 3 Jul 2024 22:54:39 +0300 Subject: [PATCH 1/7] feat: same titles for sent email groups --- .../bulk-email-tool/BulkEmailTool.jsx | 5 +- .../bulk-email-form/BulkEmailForm.jsx | 5 +- .../BulkEmailRecipient.jsx | 7 +-- .../test/BulkEmailForm.test.jsx | 13 ++--- .../BulkEmailContentHistory.jsx | 3 +- .../BulkEmailTaskManager.jsx | 10 +++- .../ViewEmailModal.jsx | 2 +- .../BulkEmailScheduledEmailsTable.jsx | 25 ++++++--- .../BulkEmailScheduledEmailsTable.test.jsx | 3 +- src/components/bulk-email-tool/utils.js | 52 +++++++++++++++++++ 10 files changed, 102 insertions(+), 23 deletions(-) create mode 100644 src/components/bulk-email-tool/utils.js diff --git a/src/components/bulk-email-tool/BulkEmailTool.jsx b/src/components/bulk-email-tool/BulkEmailTool.jsx index 1ebec152..9962d1e5 100644 --- a/src/components/bulk-email-tool/BulkEmailTool.jsx +++ b/src/components/bulk-email-tool/BulkEmailTool.jsx @@ -40,7 +40,10 @@ export default function BulkEmailTool() { />
- +
diff --git a/src/components/bulk-email-tool/bulk-email-form/BulkEmailForm.jsx b/src/components/bulk-email-tool/bulk-email-form/BulkEmailForm.jsx index 26191c62..5c731ee9 100644 --- a/src/components/bulk-email-tool/bulk-email-form/BulkEmailForm.jsx +++ b/src/components/bulk-email-tool/bulk-email-form/BulkEmailForm.jsx @@ -28,6 +28,7 @@ import { } from './data/actions'; import { editScheduledEmailThunk, postBulkEmailThunk } from './data/thunks'; import { getScheduledBulkEmailThunk } from '../bulk-email-task-manager/bulk-email-scheduled-emails-table/data/thunks'; +import { getDisplayText } from '../utils'; import './bulkEmailForm.scss'; @@ -219,7 +220,7 @@ function BulkEmailForm(props) {

{intl.formatMessage(messages.bulkEmailTaskAlertRecipients, { subject: editor.emailSubject })}

{!isScheduled && ( @@ -246,7 +247,7 @@ function BulkEmailForm(props) {

{intl.formatMessage(messages.bulkEmailTaskAlertEditingTo)}

{intl.formatMessage(messages.bulkEmailTaskAlertEditingWarning)}

diff --git a/src/components/bulk-email-tool/bulk-email-form/bulk-email-recipient/BulkEmailRecipient.jsx b/src/components/bulk-email-tool/bulk-email-form/bulk-email-recipient/BulkEmailRecipient.jsx index d60b316b..48445990 100644 --- a/src/components/bulk-email-tool/bulk-email-form/bulk-email-recipient/BulkEmailRecipient.jsx +++ b/src/components/bulk-email-tool/bulk-email-form/bulk-email-recipient/BulkEmailRecipient.jsx @@ -2,6 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Form } from '@openedx/paragon'; import { FormattedMessage } from '@edx/frontend-platform/i18n'; +import { RECIPIENTS_DISPLAY_NAMES } from '../../utils'; import './bulkEmailRecepient.scss'; @@ -41,7 +42,7 @@ export default function BulkEmailRecipient(props) { @@ -52,7 +53,7 @@ export default function BulkEmailRecipient(props) { > @@ -99,7 +100,7 @@ export default function BulkEmailRecipient(props) { > diff --git a/src/components/bulk-email-tool/bulk-email-form/test/BulkEmailForm.test.jsx b/src/components/bulk-email-tool/bulk-email-form/test/BulkEmailForm.test.jsx index b11d9edc..819ffe2b 100644 --- a/src/components/bulk-email-tool/bulk-email-form/test/BulkEmailForm.test.jsx +++ b/src/components/bulk-email-tool/bulk-email-form/test/BulkEmailForm.test.jsx @@ -13,6 +13,7 @@ import { BulkEmailContext, BulkEmailProvider } from '../../bulk-email-context'; import { formatDate } from '../../../../utils/formatDateAndTime'; import cohortFactory from '../data/__factories__/bulkEmailFormCohort.factory'; import courseModeFactory from '../data/__factories__/bulkEmailFormCourseMode.factory'; +import { RECIPIENTS_DISPLAY_NAMES } from '../../utils'; jest.mock('../../text-editor/TextEditor'); @@ -75,8 +76,8 @@ describe('bulk-email-form', () => { success: true, }); render(renderBulkEmailForm()); - fireEvent.click(screen.getByRole('checkbox', { name: 'Myself' })); - expect(screen.getByRole('checkbox', { name: 'Myself' })).toBeChecked(); + fireEvent.click(screen.getByRole('checkbox', { name: RECIPIENTS_DISPLAY_NAMES.myself })); + expect(screen.getByRole('checkbox', { name: RECIPIENTS_DISPLAY_NAMES.myself })).toBeChecked(); fireEvent.change(screen.getByRole('textbox', { name: 'Subject' }), { target: { value: 'test subject' } }); fireEvent.change(screen.getByTestId('textEditor'), { target: { value: 'test body' } }); fireEvent.click(screen.getByText('Send email')); @@ -90,7 +91,7 @@ describe('bulk-email-form', () => { axiosMock.onPost(`${getConfig().LMS_BASE_URL}/courses/test/instructor/api/send_email`).reply(500); render(renderBulkEmailForm()); const subjectLine = screen.getByRole('textbox', { name: 'Subject' }); - const recipient = screen.getByRole('checkbox', { name: 'Myself' }); + const recipient = screen.getByRole('checkbox', { name: RECIPIENTS_DISPLAY_NAMES.myself }); fireEvent.click(recipient); fireEvent.change(subjectLine, { target: { value: 'test subject' } }); fireEvent.change(screen.getByTestId('textEditor'), { target: { value: 'test body' } }); @@ -99,9 +100,9 @@ describe('bulk-email-form', () => { fireEvent.click(await screen.findByRole('button', { name: /continue/i })); expect(await screen.findByText('An error occured while attempting to send the email.')).toBeInTheDocument(); }); - test('Checking "All Learners" disables each learner group', async () => { + test('Checking "All Students" disables each learner group', async () => { render(renderBulkEmailForm()); - fireEvent.click(screen.getByRole('checkbox', { name: 'All Learners' })); + fireEvent.click(screen.getByRole('checkbox', { name: RECIPIENTS_DISPLAY_NAMES.learners })); const verifiedLearners = screen.getByRole('checkbox', { name: 'Learners in the Verified Certificate Track' }); const auditLearners = screen.getByRole('checkbox', { name: 'Learners in the Audit Track' }); const { cohorts } = cohortFactory.build(); @@ -130,7 +131,7 @@ describe('bulk-email-form', () => { test('Adds scheduling data to POST requests when schedule is selected', async () => { const postBulkEmailInstructorTask = jest.spyOn(bulkEmailFormApi, 'postBulkEmailInstructorTask'); render(renderBulkEmailForm()); - fireEvent.click(screen.getByRole('checkbox', { name: 'Myself' })); + fireEvent.click(screen.getByRole('checkbox', { name: RECIPIENTS_DISPLAY_NAMES.myself })); fireEvent.change(screen.getByRole('textbox', { name: 'Subject' }), { target: { value: 'test subject' } }); fireEvent.change(screen.getByTestId('textEditor'), { target: { value: 'test body' } }); const scheduleCheckbox = screen.getByText('Schedule this email for a future date'); diff --git a/src/components/bulk-email-tool/bulk-email-task-manager/BulkEmailContentHistory.jsx b/src/components/bulk-email-tool/bulk-email-task-manager/BulkEmailContentHistory.jsx index 0539931c..eedb4d7e 100644 --- a/src/components/bulk-email-tool/bulk-email-task-manager/BulkEmailContentHistory.jsx +++ b/src/components/bulk-email-tool/bulk-email-task-manager/BulkEmailContentHistory.jsx @@ -13,6 +13,7 @@ import messages from './messages'; import { getSentEmailHistory } from './data/api'; import BulkEmailTaskManagerTable from './BulkEmailHistoryTable'; import ViewEmailModal from './ViewEmailModal'; +import { HISTORY_RECIPIENTS_DISPLAY_NAMES } from '../utils'; function BulkEmailContentHistory() { const intl = useIntl(); @@ -56,7 +57,7 @@ function BulkEmailContentHistory() { const tableData = emailHistoryData?.map((item) => ({ ...item, subject: item.email.subject, - sent_to: item.sent_to.join(', '), + sent_to: item.sent_to.map((recipient) => HISTORY_RECIPIENTS_DISPLAY_NAMES[recipient] || recipient).join(', '), created: new Date(item.created).toLocaleString(), })); return tableData || []; diff --git a/src/components/bulk-email-tool/bulk-email-task-manager/BulkEmailTaskManager.jsx b/src/components/bulk-email-tool/bulk-email-task-manager/BulkEmailTaskManager.jsx index 2db499a9..d0149150 100644 --- a/src/components/bulk-email-tool/bulk-email-task-manager/BulkEmailTaskManager.jsx +++ b/src/components/bulk-email-tool/bulk-email-task-manager/BulkEmailTaskManager.jsx @@ -9,14 +9,14 @@ import messages from './messages'; import BulkEmailScheduledEmailsTable from './bulk-email-scheduled-emails-table'; import BulkEmailPendingTasksAlert from './BulkEmailPendingTasksAlert'; -function BulkEmailTaskManager({ courseId }) { +function BulkEmailTaskManager({ courseId, courseModes }) { const intl = useIntl(); return (
{getConfig().SCHEDULE_EMAIL_SECTION && (

{intl.formatMessage(messages.scheduledEmailsTableHeader)}

- +
)}
@@ -36,6 +36,12 @@ function BulkEmailTaskManager({ courseId }) { BulkEmailTaskManager.propTypes = { courseId: PropTypes.string.isRequired, + courseModes: PropTypes.arrayOf( + PropTypes.shape({ + slug: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + }), + ).isRequired, }; export default BulkEmailTaskManager; diff --git a/src/components/bulk-email-tool/bulk-email-task-manager/ViewEmailModal.jsx b/src/components/bulk-email-tool/bulk-email-task-manager/ViewEmailModal.jsx index 8ef27958..f709e3ae 100644 --- a/src/components/bulk-email-tool/bulk-email-task-manager/ViewEmailModal.jsx +++ b/src/components/bulk-email-tool/bulk-email-task-manager/ViewEmailModal.jsx @@ -34,7 +34,7 @@ function ViewEmailModal({

{messageContent.created}

-

{intl.formatMessage(messages.modalMessageSentTo)}

+

{intl.formatMessage(messages.modalMessageSentTo)}

{messageContent.sent_to}


diff --git a/src/components/bulk-email-tool/bulk-email-task-manager/bulk-email-scheduled-emails-table/BulkEmailScheduledEmailsTable.jsx b/src/components/bulk-email-tool/bulk-email-task-manager/bulk-email-scheduled-emails-table/BulkEmailScheduledEmailsTable.jsx index a7bdd685..b7143577 100644 --- a/src/components/bulk-email-tool/bulk-email-task-manager/bulk-email-scheduled-emails-table/BulkEmailScheduledEmailsTable.jsx +++ b/src/components/bulk-email-tool/bulk-email-task-manager/bulk-email-scheduled-emails-table/BulkEmailScheduledEmailsTable.jsx @@ -4,6 +4,7 @@ import React, { useCallback, useContext, useState, useEffect, } from 'react'; +import PropTypes from 'prop-types'; import { useIntl } from '@edx/frontend-platform/i18n'; import { Alert, DataTable, Icon, IconButton, useToggle, @@ -19,8 +20,9 @@ import ViewEmailModal from '../ViewEmailModal'; import { copyToEditor } from '../../bulk-email-form/data/actions'; import TaskAlertModal from '../../task-alert-modal'; import { formatDate, formatTime } from '../../../../utils/formatDateAndTime'; +import { getDisplayText, getRecipientFromDisplayText } from '../../utils'; -function flattenScheduledEmailsArray(emails) { +function flattenScheduledEmailsArray(emails, courseModes) { return emails.map((email) => ({ schedulingId: email.id, emailId: email.courseEmail.id, @@ -28,11 +30,12 @@ function flattenScheduledEmailsArray(emails) { taskDue: new Date(email.taskDue).toLocaleString(), taskDueUTC: email.taskDue, ...email.courseEmail, - targets: email.courseEmail.targets.join(', '), + targets: email.courseEmail.targets + .map((recipient) => getDisplayText(recipient, courseModes)).join(', '), })); } -function BulkEmailScheduledEmailsTable() { +function BulkEmailScheduledEmailsTable({ courseModes }) { const intl = useIntl(); const { courseId } = useParams(); const [{ scheduledEmailsTable }, dispatch] = useContext(BulkEmailContext); @@ -45,8 +48,8 @@ function BulkEmailScheduledEmailsTable() { const [currentTask, setCurrentTask] = useState({}); useEffect(() => { - setTableData(flattenScheduledEmailsArray(scheduledEmailsTable.results)); - }, [scheduledEmailsTable.results]); + setTableData(flattenScheduledEmailsArray(scheduledEmailsTable.results, courseModes)); + }, [scheduledEmailsTable.results, courseModes]); const fetchTableData = useCallback((args) => { dispatch(getScheduledBulkEmailThunk(courseId, args.pageIndex + 1)); @@ -97,7 +100,8 @@ function BulkEmailScheduledEmailsTable() { }, } = row; const dateTime = new Date(taskDueUTC); - const emailRecipients = targets.replaceAll('-', ':').split(', '); + const emailRecipients = targets + .split(', ').map((recipient) => getRecipientFromDisplayText(recipient, courseModes)); const scheduleDate = formatDate(dateTime); const scheduleTime = formatTime(dateTime); dispatch( @@ -197,4 +201,13 @@ function BulkEmailScheduledEmailsTable() { ); } +BulkEmailScheduledEmailsTable.propTypes = { + courseModes: PropTypes.arrayOf( + PropTypes.shape({ + slug: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + }), + ).isRequired, +}; + export default BulkEmailScheduledEmailsTable; diff --git a/src/components/bulk-email-tool/bulk-email-task-manager/bulk-email-scheduled-emails-table/test/BulkEmailScheduledEmailsTable.test.jsx b/src/components/bulk-email-tool/bulk-email-task-manager/bulk-email-scheduled-emails-table/test/BulkEmailScheduledEmailsTable.test.jsx index 3495bef2..f461b2d5 100644 --- a/src/components/bulk-email-tool/bulk-email-task-manager/bulk-email-scheduled-emails-table/test/BulkEmailScheduledEmailsTable.test.jsx +++ b/src/components/bulk-email-tool/bulk-email-task-manager/bulk-email-scheduled-emails-table/test/BulkEmailScheduledEmailsTable.test.jsx @@ -13,6 +13,7 @@ import { BulkEmailProvider } from '../../../bulk-email-context'; import BulkEmailScheduledEmailsTable from '..'; import scheduledEmailsFactory from './__factories__/scheduledEmails.factory'; import * as actions from '../../../bulk-email-form/data/actions'; +import { RECIPIENTS_DISPLAY_NAMES } from '../../../utils'; jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), @@ -42,7 +43,7 @@ describe('BulkEmailScheduledEmailsTable', () => { .onGet(`${getConfig().LMS_BASE_URL}/api/instructor_task/v1/schedules/test-id/bulk_email/?page=1`) .reply(200, scheduledEmailsFactory.build(1)); render(renderBulkEmailScheduledEmailsTable()); - expect(await screen.findByText('learners')).toBeTruthy(); + expect(await screen.findByText(RECIPIENTS_DISPLAY_NAMES.learners)).toBeTruthy(); expect(await screen.findByText('subject')).toBeTruthy(); expect(await screen.findByText('edx')).toBeTruthy(); expect(await screen.findByLabelText('View')).toBeTruthy(); diff --git a/src/components/bulk-email-tool/utils.js b/src/components/bulk-email-tool/utils.js new file mode 100644 index 00000000..2e454128 --- /dev/null +++ b/src/components/bulk-email-tool/utils.js @@ -0,0 +1,52 @@ +export const RECIPIENTS_DISPLAY_NAMES = { + myself: 'Myself', + staff: 'Staff/Administrators', + learners: 'All Learners', +}; + +export const HISTORY_RECIPIENTS_DISPLAY_NAMES = { + 'Staff and instructors': 'Staff/Administrators', + 'All students': 'All Learners', +}; + +// Output: { 'Myself': 'myself', 'Staff/Administrators': 'staff', 'All Learners': 'learners' } +export const REVERSE_RECIPIENTS_DISPLAY_NAMES = Object.fromEntries( + Object.entries(RECIPIENTS_DISPLAY_NAMES).map(([key, value]) => [value, key]), +); + +export const getDisplayText = (recipient, courseModes) => { + const normalizedRecipient = recipient.replace(/-/, ':'); + + if (normalizedRecipient.startsWith('track') && courseModes) { + const courseModeSlug = normalizedRecipient.split(':')[1]; + const courseMode = courseModes.find((mode) => mode.slug === courseModeSlug); + if (courseMode) { + return `Learners in the ${courseMode.name} Track`; + } + } + + if (normalizedRecipient.startsWith('cohort')) { + const cohort = normalizedRecipient.replace(/:/, ': '); + return cohort.charAt(0).toUpperCase() + cohort.slice(1); + } + + return RECIPIENTS_DISPLAY_NAMES[recipient] || recipient; +}; + +export const getRecipientFromDisplayText = (displayText, courseModes) => { + const trackMatch = displayText.match(/^Learners in the (.+) Track$/); + if (trackMatch) { + const courseModeName = trackMatch[1]; + const courseMode = courseModes.find(mode => mode.name === courseModeName); + if (courseMode) { + return `track:${courseMode.slug}`; + } + } + + const cohortMatch = displayText.match(/^Cohort: (.+)$/); + if (cohortMatch) { + return `cohort:${cohortMatch[1].replace(' ', '-')}`; + } + + return REVERSE_RECIPIENTS_DISPLAY_NAMES[displayText] || displayText; +}; From 8a915006235801fb3ed700daa7eff86bde239b33 Mon Sep 17 00:00:00 2001 From: Kyrylo Hudym-Levkovych Date: Thu, 4 Jul 2024 17:42:49 +0300 Subject: [PATCH 2/7] feat: aligned the checkbox labels with the BE --- .../bulk-email-tool/BulkEmailTool.jsx | 5 +- .../bulk-email-form/BulkEmailForm.jsx | 6 +-- .../BulkEmailContentHistory.jsx | 3 +- .../BulkEmailTaskManager.jsx | 10 +--- .../BulkEmailScheduledEmailsTable.jsx | 10 ++-- src/components/bulk-email-tool/utils.js | 48 ++----------------- 6 files changed, 16 insertions(+), 66 deletions(-) diff --git a/src/components/bulk-email-tool/BulkEmailTool.jsx b/src/components/bulk-email-tool/BulkEmailTool.jsx index 9962d1e5..1ebec152 100644 --- a/src/components/bulk-email-tool/BulkEmailTool.jsx +++ b/src/components/bulk-email-tool/BulkEmailTool.jsx @@ -40,10 +40,7 @@ export default function BulkEmailTool() { />
- +
diff --git a/src/components/bulk-email-tool/bulk-email-form/BulkEmailForm.jsx b/src/components/bulk-email-tool/bulk-email-form/BulkEmailForm.jsx index 5c731ee9..1a78a510 100644 --- a/src/components/bulk-email-tool/bulk-email-form/BulkEmailForm.jsx +++ b/src/components/bulk-email-tool/bulk-email-form/BulkEmailForm.jsx @@ -28,7 +28,7 @@ import { } from './data/actions'; import { editScheduledEmailThunk, postBulkEmailThunk } from './data/thunks'; import { getScheduledBulkEmailThunk } from '../bulk-email-task-manager/bulk-email-scheduled-emails-table/data/thunks'; -import { getDisplayText } from '../utils'; +import { getDisplayTextFromRecipient } from '../utils'; import './bulkEmailForm.scss'; @@ -220,7 +220,7 @@ function BulkEmailForm(props) {

{intl.formatMessage(messages.bulkEmailTaskAlertRecipients, { subject: editor.emailSubject })}

    {editor.emailRecipients.map((group) => ( -
  • {getDisplayText(group, courseModes)}
  • +
  • {getDisplayTextFromRecipient(group)}
  • ))}
{!isScheduled && ( @@ -247,7 +247,7 @@ function BulkEmailForm(props) {

{intl.formatMessage(messages.bulkEmailTaskAlertEditingTo)}

    {editor.emailRecipients.map((group) => ( -
  • {getDisplayText(group, courseModes)}
  • +
  • {getDisplayTextFromRecipient(group)}
  • ))}

{intl.formatMessage(messages.bulkEmailTaskAlertEditingWarning)}

diff --git a/src/components/bulk-email-tool/bulk-email-task-manager/BulkEmailContentHistory.jsx b/src/components/bulk-email-tool/bulk-email-task-manager/BulkEmailContentHistory.jsx index eedb4d7e..0539931c 100644 --- a/src/components/bulk-email-tool/bulk-email-task-manager/BulkEmailContentHistory.jsx +++ b/src/components/bulk-email-tool/bulk-email-task-manager/BulkEmailContentHistory.jsx @@ -13,7 +13,6 @@ import messages from './messages'; import { getSentEmailHistory } from './data/api'; import BulkEmailTaskManagerTable from './BulkEmailHistoryTable'; import ViewEmailModal from './ViewEmailModal'; -import { HISTORY_RECIPIENTS_DISPLAY_NAMES } from '../utils'; function BulkEmailContentHistory() { const intl = useIntl(); @@ -57,7 +56,7 @@ function BulkEmailContentHistory() { const tableData = emailHistoryData?.map((item) => ({ ...item, subject: item.email.subject, - sent_to: item.sent_to.map((recipient) => HISTORY_RECIPIENTS_DISPLAY_NAMES[recipient] || recipient).join(', '), + sent_to: item.sent_to.join(', '), created: new Date(item.created).toLocaleString(), })); return tableData || []; diff --git a/src/components/bulk-email-tool/bulk-email-task-manager/BulkEmailTaskManager.jsx b/src/components/bulk-email-tool/bulk-email-task-manager/BulkEmailTaskManager.jsx index d0149150..2db499a9 100644 --- a/src/components/bulk-email-tool/bulk-email-task-manager/BulkEmailTaskManager.jsx +++ b/src/components/bulk-email-tool/bulk-email-task-manager/BulkEmailTaskManager.jsx @@ -9,14 +9,14 @@ import messages from './messages'; import BulkEmailScheduledEmailsTable from './bulk-email-scheduled-emails-table'; import BulkEmailPendingTasksAlert from './BulkEmailPendingTasksAlert'; -function BulkEmailTaskManager({ courseId, courseModes }) { +function BulkEmailTaskManager({ courseId }) { const intl = useIntl(); return (
{getConfig().SCHEDULE_EMAIL_SECTION && (

{intl.formatMessage(messages.scheduledEmailsTableHeader)}

- +
)}
@@ -36,12 +36,6 @@ function BulkEmailTaskManager({ courseId, courseModes }) { BulkEmailTaskManager.propTypes = { courseId: PropTypes.string.isRequired, - courseModes: PropTypes.arrayOf( - PropTypes.shape({ - slug: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - }), - ).isRequired, }; export default BulkEmailTaskManager; diff --git a/src/components/bulk-email-tool/bulk-email-task-manager/bulk-email-scheduled-emails-table/BulkEmailScheduledEmailsTable.jsx b/src/components/bulk-email-tool/bulk-email-task-manager/bulk-email-scheduled-emails-table/BulkEmailScheduledEmailsTable.jsx index b7143577..3690c6f6 100644 --- a/src/components/bulk-email-tool/bulk-email-task-manager/bulk-email-scheduled-emails-table/BulkEmailScheduledEmailsTable.jsx +++ b/src/components/bulk-email-tool/bulk-email-task-manager/bulk-email-scheduled-emails-table/BulkEmailScheduledEmailsTable.jsx @@ -20,9 +20,9 @@ import ViewEmailModal from '../ViewEmailModal'; import { copyToEditor } from '../../bulk-email-form/data/actions'; import TaskAlertModal from '../../task-alert-modal'; import { formatDate, formatTime } from '../../../../utils/formatDateAndTime'; -import { getDisplayText, getRecipientFromDisplayText } from '../../utils'; +import { getDisplayTextFromRecipient, getRecipientFromDisplayText } from '../../utils'; -function flattenScheduledEmailsArray(emails, courseModes) { +function flattenScheduledEmailsArray(emails) { return emails.map((email) => ({ schedulingId: email.id, emailId: email.courseEmail.id, @@ -30,8 +30,7 @@ function flattenScheduledEmailsArray(emails, courseModes) { taskDue: new Date(email.taskDue).toLocaleString(), taskDueUTC: email.taskDue, ...email.courseEmail, - targets: email.courseEmail.targets - .map((recipient) => getDisplayText(recipient, courseModes)).join(', '), + targets: email.courseEmail.targets.map(getDisplayTextFromRecipient).join(', '), })); } @@ -100,8 +99,7 @@ function BulkEmailScheduledEmailsTable({ courseModes }) { }, } = row; const dateTime = new Date(taskDueUTC); - const emailRecipients = targets - .split(', ').map((recipient) => getRecipientFromDisplayText(recipient, courseModes)); + const emailRecipients = targets.replaceAll('-', ':').split(', ').map(getRecipientFromDisplayText); const scheduleDate = formatDate(dateTime); const scheduleTime = formatTime(dateTime); dispatch( diff --git a/src/components/bulk-email-tool/utils.js b/src/components/bulk-email-tool/utils.js index 2e454128..2a37c880 100644 --- a/src/components/bulk-email-tool/utils.js +++ b/src/components/bulk-email-tool/utils.js @@ -1,52 +1,14 @@ export const RECIPIENTS_DISPLAY_NAMES = { myself: 'Myself', - staff: 'Staff/Administrators', - learners: 'All Learners', + staff: 'Staff and instructors', + learners: 'All students', }; -export const HISTORY_RECIPIENTS_DISPLAY_NAMES = { - 'Staff and instructors': 'Staff/Administrators', - 'All students': 'All Learners', -}; - -// Output: { 'Myself': 'myself', 'Staff/Administrators': 'staff', 'All Learners': 'learners' } +// Output: { 'Myself': 'myself', 'Staff and instructors': 'staff', 'All students': 'learners' } export const REVERSE_RECIPIENTS_DISPLAY_NAMES = Object.fromEntries( Object.entries(RECIPIENTS_DISPLAY_NAMES).map(([key, value]) => [value, key]), ); -export const getDisplayText = (recipient, courseModes) => { - const normalizedRecipient = recipient.replace(/-/, ':'); - - if (normalizedRecipient.startsWith('track') && courseModes) { - const courseModeSlug = normalizedRecipient.split(':')[1]; - const courseMode = courseModes.find((mode) => mode.slug === courseModeSlug); - if (courseMode) { - return `Learners in the ${courseMode.name} Track`; - } - } +export const getDisplayTextFromRecipient = (recipient) => RECIPIENTS_DISPLAY_NAMES[recipient] || recipient; - if (normalizedRecipient.startsWith('cohort')) { - const cohort = normalizedRecipient.replace(/:/, ': '); - return cohort.charAt(0).toUpperCase() + cohort.slice(1); - } - - return RECIPIENTS_DISPLAY_NAMES[recipient] || recipient; -}; - -export const getRecipientFromDisplayText = (displayText, courseModes) => { - const trackMatch = displayText.match(/^Learners in the (.+) Track$/); - if (trackMatch) { - const courseModeName = trackMatch[1]; - const courseMode = courseModes.find(mode => mode.name === courseModeName); - if (courseMode) { - return `track:${courseMode.slug}`; - } - } - - const cohortMatch = displayText.match(/^Cohort: (.+)$/); - if (cohortMatch) { - return `cohort:${cohortMatch[1].replace(' ', '-')}`; - } - - return REVERSE_RECIPIENTS_DISPLAY_NAMES[displayText] || displayText; -}; +export const getRecipientFromDisplayText = (recipient) => REVERSE_RECIPIENTS_DISPLAY_NAMES[recipient] || recipient; From 0d1741aef99f2c7e40fcfe5bf4084679b42fcb6f Mon Sep 17 00:00:00 2001 From: ihor-romaniuk Date: Mon, 9 Sep 2024 12:23:25 +0200 Subject: [PATCH 3/7] fix: refactor the code --- .../bulk-email-form/BulkEmailForm.jsx | 4 +-- .../BulkEmailRecipient.jsx | 36 +++++-------------- .../test/BulkEmailForm.test.jsx | 13 ++++--- .../BulkEmailScheduledEmailsTable.jsx | 27 +++++--------- .../BulkEmailScheduledEmailsTable.test.jsx | 3 +- src/components/bulk-email-tool/constants.js | 8 +++++ src/components/bulk-email-tool/messages.js | 21 +++++++++++ src/components/bulk-email-tool/utils.js | 29 +++++++++------ 8 files changed, 74 insertions(+), 67 deletions(-) create mode 100644 src/components/bulk-email-tool/constants.js create mode 100644 src/components/bulk-email-tool/messages.js diff --git a/src/components/bulk-email-tool/bulk-email-form/BulkEmailForm.jsx b/src/components/bulk-email-tool/bulk-email-form/BulkEmailForm.jsx index 1a78a510..9389edb4 100644 --- a/src/components/bulk-email-tool/bulk-email-form/BulkEmailForm.jsx +++ b/src/components/bulk-email-tool/bulk-email-form/BulkEmailForm.jsx @@ -220,7 +220,7 @@ function BulkEmailForm(props) {

{intl.formatMessage(messages.bulkEmailTaskAlertRecipients, { subject: editor.emailSubject })}

    {editor.emailRecipients.map((group) => ( -
  • {getDisplayTextFromRecipient(group)}
  • +
  • {getDisplayTextFromRecipient(intl, group)}
  • ))}
{!isScheduled && ( @@ -247,7 +247,7 @@ function BulkEmailForm(props) {

{intl.formatMessage(messages.bulkEmailTaskAlertEditingTo)}

    {editor.emailRecipients.map((group) => ( -
  • {getDisplayTextFromRecipient(group)}
  • +
  • {getDisplayTextFromRecipient(intl, group)}
  • ))}

{intl.formatMessage(messages.bulkEmailTaskAlertEditingWarning)}

diff --git a/src/components/bulk-email-tool/bulk-email-form/bulk-email-recipient/BulkEmailRecipient.jsx b/src/components/bulk-email-tool/bulk-email-form/bulk-email-recipient/BulkEmailRecipient.jsx index 48445990..98ca9b67 100644 --- a/src/components/bulk-email-tool/bulk-email-form/bulk-email-recipient/BulkEmailRecipient.jsx +++ b/src/components/bulk-email-tool/bulk-email-form/bulk-email-recipient/BulkEmailRecipient.jsx @@ -1,19 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Form } from '@openedx/paragon'; -import { FormattedMessage } from '@edx/frontend-platform/i18n'; -import { RECIPIENTS_DISPLAY_NAMES } from '../../utils'; +import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; +import { getDisplayTextFromRecipient } from '../../utils'; +import { DEFAULT_RECIPIENTS_GROUPS } from '../../constants'; import './bulkEmailRecepient.scss'; -const DEFAULT_GROUPS = { - SELF: 'myself', - STAFF: 'staff', - ALL_LEARNERS: 'learners', - VERIFIED: 'track:verified', - AUDIT: 'track:audit', -}; - export default function BulkEmailRecipient(props) { const { handleCheckboxes, @@ -21,6 +14,7 @@ export default function BulkEmailRecipient(props) { additionalCohorts, courseModes, } = props; + const intl = useIntl(); const hasCourseModes = courseModes && courseModes.length > 1; return ( @@ -40,22 +34,14 @@ export default function BulkEmailRecipient(props) { value={selectedGroups} > - + {getDisplayTextFromRecipient(intl, DEFAULT_RECIPIENTS_GROUPS.SELF)} - + {getDisplayTextFromRecipient(intl, DEFAULT_RECIPIENTS_GROUPS.STAFF)} { // additional modes @@ -64,7 +50,7 @@ export default function BulkEmailRecipient(props) { group === DEFAULT_GROUPS.ALL_LEARNERS)} + disabled={selectedGroups.find((group) => group === DEFAULT_RECIPIENTS_GROUPS.ALL_LEARNERS)} className="col col-lg-4 col-sm-6 col-12" > group === DEFAULT_GROUPS.ALL_LEARNERS)} + disabled={selectedGroups.find((group) => group === DEFAULT_RECIPIENTS_GROUPS.ALL_LEARNERS)} className="col col-lg-4 col-sm-6 col-12" > - + {getDisplayTextFromRecipient(intl, DEFAULT_RECIPIENTS_GROUPS.ALL_LEARNERS)} {!props.isValid && ( diff --git a/src/components/bulk-email-tool/bulk-email-form/test/BulkEmailForm.test.jsx b/src/components/bulk-email-tool/bulk-email-form/test/BulkEmailForm.test.jsx index 819ffe2b..afacbccd 100644 --- a/src/components/bulk-email-tool/bulk-email-form/test/BulkEmailForm.test.jsx +++ b/src/components/bulk-email-tool/bulk-email-form/test/BulkEmailForm.test.jsx @@ -13,7 +13,6 @@ import { BulkEmailContext, BulkEmailProvider } from '../../bulk-email-context'; import { formatDate } from '../../../../utils/formatDateAndTime'; import cohortFactory from '../data/__factories__/bulkEmailFormCohort.factory'; import courseModeFactory from '../data/__factories__/bulkEmailFormCourseMode.factory'; -import { RECIPIENTS_DISPLAY_NAMES } from '../../utils'; jest.mock('../../text-editor/TextEditor'); @@ -76,8 +75,8 @@ describe('bulk-email-form', () => { success: true, }); render(renderBulkEmailForm()); - fireEvent.click(screen.getByRole('checkbox', { name: RECIPIENTS_DISPLAY_NAMES.myself })); - expect(screen.getByRole('checkbox', { name: RECIPIENTS_DISPLAY_NAMES.myself })).toBeChecked(); + fireEvent.click(screen.getByRole('checkbox', { name: 'Myself' })); + expect(screen.getByRole('checkbox', { name: 'Myself' })).toBeChecked(); fireEvent.change(screen.getByRole('textbox', { name: 'Subject' }), { target: { value: 'test subject' } }); fireEvent.change(screen.getByTestId('textEditor'), { target: { value: 'test body' } }); fireEvent.click(screen.getByText('Send email')); @@ -91,7 +90,7 @@ describe('bulk-email-form', () => { axiosMock.onPost(`${getConfig().LMS_BASE_URL}/courses/test/instructor/api/send_email`).reply(500); render(renderBulkEmailForm()); const subjectLine = screen.getByRole('textbox', { name: 'Subject' }); - const recipient = screen.getByRole('checkbox', { name: RECIPIENTS_DISPLAY_NAMES.myself }); + const recipient = screen.getByRole('checkbox', { name: 'Myself' }); fireEvent.click(recipient); fireEvent.change(subjectLine, { target: { value: 'test subject' } }); fireEvent.change(screen.getByTestId('textEditor'), { target: { value: 'test body' } }); @@ -100,9 +99,9 @@ describe('bulk-email-form', () => { fireEvent.click(await screen.findByRole('button', { name: /continue/i })); expect(await screen.findByText('An error occured while attempting to send the email.')).toBeInTheDocument(); }); - test('Checking "All Students" disables each learner group', async () => { + test('Checking "All learners" disables each learner group', async () => { render(renderBulkEmailForm()); - fireEvent.click(screen.getByRole('checkbox', { name: RECIPIENTS_DISPLAY_NAMES.learners })); + fireEvent.click(screen.getByRole('checkbox', { name: 'All learners' })); const verifiedLearners = screen.getByRole('checkbox', { name: 'Learners in the Verified Certificate Track' }); const auditLearners = screen.getByRole('checkbox', { name: 'Learners in the Audit Track' }); const { cohorts } = cohortFactory.build(); @@ -131,7 +130,7 @@ describe('bulk-email-form', () => { test('Adds scheduling data to POST requests when schedule is selected', async () => { const postBulkEmailInstructorTask = jest.spyOn(bulkEmailFormApi, 'postBulkEmailInstructorTask'); render(renderBulkEmailForm()); - fireEvent.click(screen.getByRole('checkbox', { name: RECIPIENTS_DISPLAY_NAMES.myself })); + fireEvent.click(screen.getByRole('checkbox', { name: 'Myself' })); fireEvent.change(screen.getByRole('textbox', { name: 'Subject' }), { target: { value: 'test subject' } }); fireEvent.change(screen.getByTestId('textEditor'), { target: { value: 'test body' } }); const scheduleCheckbox = screen.getByText('Schedule this email for a future date'); diff --git a/src/components/bulk-email-tool/bulk-email-task-manager/bulk-email-scheduled-emails-table/BulkEmailScheduledEmailsTable.jsx b/src/components/bulk-email-tool/bulk-email-task-manager/bulk-email-scheduled-emails-table/BulkEmailScheduledEmailsTable.jsx index 3690c6f6..98f23e73 100644 --- a/src/components/bulk-email-tool/bulk-email-task-manager/bulk-email-scheduled-emails-table/BulkEmailScheduledEmailsTable.jsx +++ b/src/components/bulk-email-tool/bulk-email-task-manager/bulk-email-scheduled-emails-table/BulkEmailScheduledEmailsTable.jsx @@ -4,7 +4,6 @@ import React, { useCallback, useContext, useState, useEffect, } from 'react'; -import PropTypes from 'prop-types'; import { useIntl } from '@edx/frontend-platform/i18n'; import { Alert, DataTable, Icon, IconButton, useToggle, @@ -20,9 +19,9 @@ import ViewEmailModal from '../ViewEmailModal'; import { copyToEditor } from '../../bulk-email-form/data/actions'; import TaskAlertModal from '../../task-alert-modal'; import { formatDate, formatTime } from '../../../../utils/formatDateAndTime'; -import { getDisplayTextFromRecipient, getRecipientFromDisplayText } from '../../utils'; +import { getDisplayTextFromRecipient } from '../../utils'; -function flattenScheduledEmailsArray(emails) { +function flattenScheduledEmailsArray(intl, emails) { return emails.map((email) => ({ schedulingId: email.id, emailId: email.courseEmail.id, @@ -30,11 +29,12 @@ function flattenScheduledEmailsArray(emails) { taskDue: new Date(email.taskDue).toLocaleString(), taskDueUTC: email.taskDue, ...email.courseEmail, - targets: email.courseEmail.targets.map(getDisplayTextFromRecipient).join(', '), + targets: email.courseEmail.targets.join(', '), + targetsText: email.courseEmail.targets.map((mess) => getDisplayTextFromRecipient(intl, mess)).join(', '), })); } -function BulkEmailScheduledEmailsTable({ courseModes }) { +function BulkEmailScheduledEmailsTable() { const intl = useIntl(); const { courseId } = useParams(); const [{ scheduledEmailsTable }, dispatch] = useContext(BulkEmailContext); @@ -47,8 +47,8 @@ function BulkEmailScheduledEmailsTable({ courseModes }) { const [currentTask, setCurrentTask] = useState({}); useEffect(() => { - setTableData(flattenScheduledEmailsArray(scheduledEmailsTable.results, courseModes)); - }, [scheduledEmailsTable.results, courseModes]); + setTableData(flattenScheduledEmailsArray(intl, scheduledEmailsTable.results)); + }, [intl, scheduledEmailsTable.results]); const fetchTableData = useCallback((args) => { dispatch(getScheduledBulkEmailThunk(courseId, args.pageIndex + 1)); @@ -99,7 +99,7 @@ function BulkEmailScheduledEmailsTable({ courseModes }) { }, } = row; const dateTime = new Date(taskDueUTC); - const emailRecipients = targets.replaceAll('-', ':').split(', ').map(getRecipientFromDisplayText); + const emailRecipients = targets.replaceAll('-', ':').split(', '); const scheduleDate = formatDate(dateTime); const scheduleTime = formatTime(dateTime); dispatch( @@ -157,7 +157,7 @@ function BulkEmailScheduledEmailsTable({ courseModes }) { }, { Header: intl.formatMessage(messages.bulkEmailScheduledEmailsTableSendTo), - accessor: 'targets', + accessor: 'targetsText', }, { Header: intl.formatMessage(messages.bulkEmailScheduledEmailsTableSubject), @@ -199,13 +199,4 @@ function BulkEmailScheduledEmailsTable({ courseModes }) { ); } -BulkEmailScheduledEmailsTable.propTypes = { - courseModes: PropTypes.arrayOf( - PropTypes.shape({ - slug: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - }), - ).isRequired, -}; - export default BulkEmailScheduledEmailsTable; diff --git a/src/components/bulk-email-tool/bulk-email-task-manager/bulk-email-scheduled-emails-table/test/BulkEmailScheduledEmailsTable.test.jsx b/src/components/bulk-email-tool/bulk-email-task-manager/bulk-email-scheduled-emails-table/test/BulkEmailScheduledEmailsTable.test.jsx index f461b2d5..e540f727 100644 --- a/src/components/bulk-email-tool/bulk-email-task-manager/bulk-email-scheduled-emails-table/test/BulkEmailScheduledEmailsTable.test.jsx +++ b/src/components/bulk-email-tool/bulk-email-task-manager/bulk-email-scheduled-emails-table/test/BulkEmailScheduledEmailsTable.test.jsx @@ -13,7 +13,6 @@ import { BulkEmailProvider } from '../../../bulk-email-context'; import BulkEmailScheduledEmailsTable from '..'; import scheduledEmailsFactory from './__factories__/scheduledEmails.factory'; import * as actions from '../../../bulk-email-form/data/actions'; -import { RECIPIENTS_DISPLAY_NAMES } from '../../../utils'; jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), @@ -43,7 +42,7 @@ describe('BulkEmailScheduledEmailsTable', () => { .onGet(`${getConfig().LMS_BASE_URL}/api/instructor_task/v1/schedules/test-id/bulk_email/?page=1`) .reply(200, scheduledEmailsFactory.build(1)); render(renderBulkEmailScheduledEmailsTable()); - expect(await screen.findByText(RECIPIENTS_DISPLAY_NAMES.learners)).toBeTruthy(); + expect(await screen.findByText('All learners')).toBeTruthy(); expect(await screen.findByText('subject')).toBeTruthy(); expect(await screen.findByText('edx')).toBeTruthy(); expect(await screen.findByLabelText('View')).toBeTruthy(); diff --git a/src/components/bulk-email-tool/constants.js b/src/components/bulk-email-tool/constants.js new file mode 100644 index 00000000..9cd9e4f5 --- /dev/null +++ b/src/components/bulk-email-tool/constants.js @@ -0,0 +1,8 @@ +// eslint-disable-next-line import/prefer-default-export +export const DEFAULT_RECIPIENTS_GROUPS = { + SELF: 'myself', + STAFF: 'staff', + ALL_LEARNERS: 'learners', + VERIFIED: 'track:verified', + AUDIT: 'track:audit', +}; diff --git a/src/components/bulk-email-tool/messages.js b/src/components/bulk-email-tool/messages.js new file mode 100644 index 00000000..f11a684c --- /dev/null +++ b/src/components/bulk-email-tool/messages.js @@ -0,0 +1,21 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + bulkEmailRecipientsMyselfLabel: { + id: 'bulk.email.recipients.myself.label', + defaultMessage: 'Myself', + description: 'Label for selecting the option to send a bulk email to oneself.', + }, + bulkEmailRecipientsStaffLabel: { + id: 'bulk.email.recipients.staff.label', + defaultMessage: 'Staff and instructors', + description: 'Label for selecting the option to send a bulk email to all staff and instructors.', + }, + bulkEmailRecipientsLearnersLabel: { + id: 'bulk.email.recipients.learners.label', + defaultMessage: 'All learners', + description: 'Label for selecting the option to send a bulk email to all learners.', + }, +}); + +export default messages; diff --git a/src/components/bulk-email-tool/utils.js b/src/components/bulk-email-tool/utils.js index 2a37c880..50262db2 100644 --- a/src/components/bulk-email-tool/utils.js +++ b/src/components/bulk-email-tool/utils.js @@ -1,14 +1,21 @@ -export const RECIPIENTS_DISPLAY_NAMES = { - myself: 'Myself', - staff: 'Staff and instructors', - learners: 'All students', +import { DEFAULT_RECIPIENTS_GROUPS } from './constants'; +import messages from './messages'; + +const RECIPIENTS_DISPLAY_NAMES = { + [DEFAULT_RECIPIENTS_GROUPS.SELF]: messages.bulkEmailRecipientsMyselfLabel, + [DEFAULT_RECIPIENTS_GROUPS.STAFF]: messages.bulkEmailRecipientsStaffLabel, + [DEFAULT_RECIPIENTS_GROUPS.ALL_LEARNERS]: messages.bulkEmailRecipientsLearnersLabel, }; -// Output: { 'Myself': 'myself', 'Staff and instructors': 'staff', 'All students': 'learners' } -export const REVERSE_RECIPIENTS_DISPLAY_NAMES = Object.fromEntries( - Object.entries(RECIPIENTS_DISPLAY_NAMES).map(([key, value]) => [value, key]), +/** + * Retrieves the display text for a given recipient. + * + * @param {Object} intl - The internationalization object, provided by React Intl. + * @param {string} recipient - The recipient key used to look up the corresponding display name. + * @returns {string} - The formatted display name for the recipient, + * or the original recipient key if no display name is found. + */ +// eslint-disable-next-line import/prefer-default-export +export const getDisplayTextFromRecipient = (intl, recipient) => ( + intl.formatMessage(RECIPIENTS_DISPLAY_NAMES[recipient]) || recipient ); - -export const getDisplayTextFromRecipient = (recipient) => RECIPIENTS_DISPLAY_NAMES[recipient] || recipient; - -export const getRecipientFromDisplayText = (recipient) => REVERSE_RECIPIENTS_DISPLAY_NAMES[recipient] || recipient; From ff97bac824524a35d1741dd9ac62fe143ec8593d Mon Sep 17 00:00:00 2001 From: Ihor Romaniuk Date: Tue, 3 Dec 2024 09:38:10 +0100 Subject: [PATCH 4/7] fix: add fall back to the recipient key Co-authored-by: Braden MacDonald --- src/components/bulk-email-tool/utils.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/bulk-email-tool/utils.js b/src/components/bulk-email-tool/utils.js index 50262db2..674fabaa 100644 --- a/src/components/bulk-email-tool/utils.js +++ b/src/components/bulk-email-tool/utils.js @@ -17,5 +17,6 @@ const RECIPIENTS_DISPLAY_NAMES = { */ // eslint-disable-next-line import/prefer-default-export export const getDisplayTextFromRecipient = (intl, recipient) => ( - intl.formatMessage(RECIPIENTS_DISPLAY_NAMES[recipient]) || recipient + const msg = RECIPIENTS_DISPLAY_NAMES[recipient]; + return msg ? intl.formatMessage(msg) : recipient; // Fall back to the recipient key if no display name is found. ); From 823ae028b2010610ad874cac5f7f5082b60440ea Mon Sep 17 00:00:00 2001 From: ihor-romaniuk Date: Mon, 13 Oct 2025 09:49:13 +0200 Subject: [PATCH 5/7] fix: lint error --- src/components/bulk-email-tool/utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/bulk-email-tool/utils.js b/src/components/bulk-email-tool/utils.js index 674fabaa..5784e64f 100644 --- a/src/components/bulk-email-tool/utils.js +++ b/src/components/bulk-email-tool/utils.js @@ -16,7 +16,7 @@ const RECIPIENTS_DISPLAY_NAMES = { * or the original recipient key if no display name is found. */ // eslint-disable-next-line import/prefer-default-export -export const getDisplayTextFromRecipient = (intl, recipient) => ( +export const getDisplayTextFromRecipient = (intl, recipient) => { const msg = RECIPIENTS_DISPLAY_NAMES[recipient]; return msg ? intl.formatMessage(msg) : recipient; // Fall back to the recipient key if no display name is found. -); +}; From 323af895f0d8e025a0fdeb54596032dd0d63b5bb Mon Sep 17 00:00:00 2001 From: ihor-romaniuk Date: Tue, 2 Dec 2025 12:21:15 +0100 Subject: [PATCH 6/7] fix: remove extra class name --- .../bulk-email-tool/bulk-email-task-manager/ViewEmailModal.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/bulk-email-tool/bulk-email-task-manager/ViewEmailModal.jsx b/src/components/bulk-email-tool/bulk-email-task-manager/ViewEmailModal.jsx index f709e3ae..8ef27958 100644 --- a/src/components/bulk-email-tool/bulk-email-task-manager/ViewEmailModal.jsx +++ b/src/components/bulk-email-tool/bulk-email-task-manager/ViewEmailModal.jsx @@ -34,7 +34,7 @@ function ViewEmailModal({

{messageContent.created}

-

{intl.formatMessage(messages.modalMessageSentTo)}

+

{intl.formatMessage(messages.modalMessageSentTo)}

{messageContent.sent_to}


From 8cc8aaed2d17d31135cce104eb3ce91def53c8db Mon Sep 17 00:00:00 2001 From: ihor-romaniuk Date: Tue, 2 Dec 2025 12:35:57 +0100 Subject: [PATCH 7/7] chore: update package-lock.json --- package-lock.json | 935 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 914 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0e6120ac..5b999032 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2654,6 +2654,40 @@ "typescript": "^4.9.4" } }, + "node_modules/@emnapi/core": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", + "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", + "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", @@ -3243,6 +3277,154 @@ "devOptional": true, "license": "BSD-3-Clause" }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.3.tgz", + "integrity": "sha512-ryFMfvxxpQRsgZJqBd4wsttYQbCxsJksrv9Lw/v798JcQ8+w84mBWuXwl+TT0WJ/WrYOLaYpwQXi3sA9nTIaIg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.0" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.3.tgz", + "integrity": "sha512-yHpJYynROAj12TA6qil58hmPmAwxKKC7reUqtGLzsOHfP7/rniNGTL8tjWX6L3CTV4+5P4ypcS7Pp+7OB+8ihA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.0" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.0.tgz", + "integrity": "sha512-sBZmpwmxqwlqG9ueWFXtockhsxefaV6O84BMOrhtg/YqbTaRdqDE7hxraVE3y6gVM4eExmfzW4a8el9ArLeEiQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.0.tgz", + "integrity": "sha512-M64XVuL94OgiNHa5/m2YvEQI5q2cl9d/wk0qFTDVXcYzi43lxuiFTftMR1tOnFQovVXNZJ5TURSDK2pNe9Yzqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.0.tgz", + "integrity": "sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.0.tgz", + "integrity": "sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.0.tgz", + "integrity": "sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.0.tgz", + "integrity": "sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@img/sharp-libvips-linux-x64": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.0.tgz", @@ -3260,6 +3442,23 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.0.tgz", + "integrity": "sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@img/sharp-libvips-linuxmusl-x64": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.0.tgz", @@ -3277,6 +3476,98 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.3.tgz", + "integrity": "sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.0" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.3.tgz", + "integrity": "sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.0" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.3.tgz", + "integrity": "sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.0" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.3.tgz", + "integrity": "sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.0" + } + }, "node_modules/@img/sharp-linux-x64": { "version": "0.34.3", "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.3.tgz", @@ -3300,6 +3591,29 @@ "@img/sharp-libvips-linux-x64": "1.2.0" } }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.3.tgz", + "integrity": "sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.0" + } + }, "node_modules/@img/sharp-linuxmusl-x64": { "version": "0.34.3", "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.3.tgz", @@ -3323,6 +3637,86 @@ "@img/sharp-libvips-linuxmusl-x64": "1.2.0" } }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.3.tgz", + "integrity": "sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.4.4" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.3.tgz", + "integrity": "sha512-MjnHPnbqMXNC2UgeLJtX4XqoVHHlZNd+nPt1kRPmj63wURegwBhZlApELdtxM2OIZDRv/DFtLcNhVbd1z8GYXQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.3.tgz", + "integrity": "sha512-xuCdhH44WxuXgOM714hn4amodJMZl3OEvf0GVTm0BEyMeA2to+8HEdRPShH0SLYptJY1uBw+SCFP9WVQi1Q/cw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.3.tgz", + "integrity": "sha512-OWwz05d++TxzLEv4VnsTz5CmZ6mI6S05sfQGEMrNrQcOEERbX46332IvE7pO/EUiw7jUrrS40z/M7kPyjfl04g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@inquirer/external-editor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.2.tgz", @@ -4052,6 +4446,19 @@ "devOptional": true, "license": "MIT" }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, "node_modules/@newrelic/publish-sourcemap": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/@newrelic/publish-sourcemap/-/publish-sourcemap-5.1.4.tgz", @@ -4534,33 +4941,193 @@ "hasInstallScript": true, "license": "MIT", "optional": true, - "dependencies": { - "detect-libc": "^1.0.3", - "is-glob": "^4.0.3", - "micromatch": "^4.0.5", - "node-addon-api": "^7.0.0" - }, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { "node": ">= 10.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "@parcel/watcher-android-arm64": "2.5.1", - "@parcel/watcher-darwin-arm64": "2.5.1", - "@parcel/watcher-darwin-x64": "2.5.1", - "@parcel/watcher-freebsd-x64": "2.5.1", - "@parcel/watcher-linux-arm-glibc": "2.5.1", - "@parcel/watcher-linux-arm-musl": "2.5.1", - "@parcel/watcher-linux-arm64-glibc": "2.5.1", - "@parcel/watcher-linux-arm64-musl": "2.5.1", - "@parcel/watcher-linux-x64-glibc": "2.5.1", - "@parcel/watcher-linux-x64-musl": "2.5.1", - "@parcel/watcher-win32-arm64": "2.5.1", - "@parcel/watcher-win32-ia32": "2.5.1", - "@parcel/watcher-win32-x64": "2.5.1" } }, "node_modules/@parcel/watcher-linux-x64-glibc": { @@ -4603,6 +5170,66 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -5148,6 +5775,17 @@ "node": ">=10.13.0" } }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", @@ -5942,6 +6580,188 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@unrs/resolver-binding-linux-x64-gnu": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", @@ -5970,6 +6790,65 @@ "linux" ] }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@webassemblyjs/ast": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", @@ -11195,6 +12074,20 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",