Compare commits

...

11 Commits

Author SHA1 Message Date
Ihor Romaniuk
d9cce51d86 fix: date format depends on locale date format (#173) 2023-11-16 07:23:14 -03:00
vladislavkeblysh
925a7392cb feat: fixed lint 2023-11-14 12:37:33 -03:00
vladislavkeblysh
42bea23bd1 feat: fixed layout 2023-11-14 12:37:33 -03:00
Stanislav Lunyachek
833de88e1c fix: Missed favicon in Safari 2023-11-14 12:36:40 -03:00
Omar Al-Ithawi
bd85312ab3 feat: use atlas in make pull_translations on palm (#156)
Changes
-------
 - Bump frontend-platform to bring intl-imports.js script
 - Move all i18n imports into `src/i18n/index.js` so intl-imports.js can
   override it with latest translations
 - Add `atlas` into `make pull_translations` when `OPENEDX_ATLAS_PULL`
   environment variable is set.
 - Fixed lint rules for frontend-platform@4.1.0
 - Mock useTrackColorSchemeChoice to avoid test failures
 - Remove all broken and deprecated Tranisfex use
 - Install openedx-atlas

Refs: [FC-0012 project](https://openedx.atlassian.net/l/cp/XGS0iCcQ) implementing Translation Infrastructure OEP-58.
2023-11-03 16:57:13 -04:00
Adolfo R. Brandes
ca7bc7d359 feat: Runtime config support, take 2
Adds a couple of missing features for proper runtime configuration:

1. Favicon runtime configuration support via react-helmet

2. Placeholder values for APP_ID and MFE_CONFIG_API_URL in the sample
   .env files
2023-06-14 15:33:56 +01:00
Adolfo R. Brandes
b41a336e11 feat: Support runtime configuration
frontend-platform supports runtime configuration since 2.5.0 (see the PR
that introduced it[1], but it requires MFE cooperation.  This implements
just that: by avoiding making configuration values constant, it should
now be possible to change them after initialization.

Only a single change related to the `LMS_BASE_URL` setting was required.

[1] openedx/frontend-platform#335
2023-06-13 21:27:27 +01:00
Tobias Macey
b4032215c6 fix: Disable URL rewriting when creating links
The default behavior of the TinyMCE editor is to rewrite links that share the same
domain as the component to be relative to that path. Relative URLs will never work in
email contents, so they _always_ need to be absolute URLs. This adds the configuration
settings for `relative_urls` and `remove_script_host` in TinyMCE to always be false,
enabling it to always use absolute URLs. See
[here](https://www.tiny.cloud/docs/configure/url-handling/) for reference.
2023-06-13 13:08:14 +01:00
Ghassan Maslamani
b20eb50699 test: course url when public path is set
The commit add two tests for the following componenets:

  1. BackToInstructor
  2. BulkEmailPendingTasksAlert

  Which tests course url when public path is set to something
  other than '/' and also when it is '/'.
2023-06-06 16:55:14 +01:00
Ghassan Maslamani
5c021cdc80 fix: getting course-id when public path is set, closes #126
This change change the way course-id is retrieved, in
  1. BackToInstructor
  2. BulkEmailPendingTasksAlert
  componenets, before it was resolved by guessing course-id
  index in the url, which would not be true if the public
  path is set something other than '/'.
  Since public path would shift the index of course-id
  in the url.

  Instead the course-id is resolved through react-router just
  like the container componenet, using the `useParams` hook.
2023-06-06 16:55:14 +01:00
Mashal Malik
fa826fe687 feat: upgrade to node v18 and related fixes (#123) 2023-06-02 12:17:47 +01:00
38 changed files with 3019 additions and 26210 deletions

2
.env
View File

@@ -19,3 +19,5 @@ SEGMENT_KEY=''
SITE_NAME=''
USER_INFO_COOKIE_NAME=''
SCHEDULE_EMAIL_SECTION=''
APP_ID=''
MFE_CONFIG_API_URL=''

View File

@@ -20,3 +20,5 @@ SEGMENT_KEY=''
SITE_NAME=localhost
USER_INFO_COOKIE_NAME='edx-user-info'
SCHEDULE_EMAIL_SECTION='true'
APP_ID=''
MFE_CONFIG_API_URL=''

View File

@@ -18,3 +18,5 @@ SEGMENT_KEY=''
SITE_NAME=localhost
USER_INFO_COOKIE_NAME='edx-user-info'
SCHEDULE_EMAIL_SECTION='true'
APP_ID=''
MFE_CONFIG_API_URL=''

View File

@@ -1,3 +1,9 @@
/* eslint-disable import/no-extraneous-dependencies */
const { createConfig } = require('@edx/frontend-build');
module.exports = createConfig('eslint');
module.exports = createConfig('eslint', {
rules: {
'react/function-component-definition': 'off',
},
});

View File

@@ -9,18 +9,18 @@ on:
jobs:
tests:
runs-on: ubuntu-latest
strategy:
matrix:
node: [16]
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Nodejs Env
run: echo "NODE_VER=`cat .nvmrc`" >> $GITHUB_ENV
- name: Setup Nodejs
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
node-version: ${{ env.NODE_VER }}
- name: Install dependencies
run: npm ci
- name: Validate package-lock.json changes

View File

@@ -10,4 +10,4 @@ on:
jobs:
version-check:
uses: openedx/.github/.github/workflows/lockfileversion-check.yml@master
uses: openedx/.github/.github/workflows/lockfileversion-check-v3.yml@master

2
.nvmrc
View File

@@ -1,2 +1,2 @@
16
18

View File

@@ -1,8 +0,0 @@
[main]
host = https://www.transifex.com
[o:open-edx:p:edx-platform:r:frontend-app-communications]
file_filter = src/i18n/messages/<lang>.json
source_file = src/i18n/transifex_input.json
source_lang = en
type = KEYVALUEJSON

View File

@@ -1,6 +1,4 @@
export TRANSIFEX_RESOURCE = frontend-app-communications
transifex_langs = "ar,fr,es_419,zh_CN"
intl_imports = ./node_modules/.bin/intl-imports.js
transifex_utils = ./node_modules/.bin/transifex-utils.js
i18n = ./src/i18n
transifex_input = $(i18n)/transifex_input.json
@@ -31,20 +29,17 @@ detect_changed_source_translations:
# Checking for changed translations...
git diff --exit-code $(i18n)
# Pushes translations to Transifex. You must run make extract_translations first.
push_translations:
# Pushing strings to Transifex...
tx push -s
# Fetching hashes from Transifex...
./node_modules/@edx/reactifex/bash_scripts/get_hashed_strings_v3.sh
# Writing out comments to file...
$(transifex_utils) $(transifex_temp) --comments --v3-scripts-path
# Pushing comments to Transifex...
./node_modules/@edx/reactifex/bash_scripts/put_comments_v3.sh
# Pulls translations from Transifex.
pull_translations:
tx pull -t -f --mode reviewed --languages=$(transifex_langs)
rm -rf src/i18n/messages
mkdir src/i18n/messages
cd src/i18n/messages \
&& atlas pull \
translations/frontend-component-header/src/i18n/messages:frontend-component-header \
translations/frontend-component-footer/src/i18n/messages:frontend-component-footer \
translations/paragon/src/i18n/messages:paragon \
translations/frontend-app-communications/src/i18n/messages:frontend-app-communications
$(intl_imports) frontend-component-header frontend-component-footer paragon frontend-app-communications
# This target is used by Travis.
validate-no-uncommitted-package-lock-changes:

28879
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -34,9 +34,10 @@
},
"dependencies": {
"@edx/brand": "npm:@edx/brand-openedx@1.1.0",
"@edx/frontend-component-footer": "11.6.0",
"@edx/frontend-component-header": "3.5.0",
"@edx/frontend-platform": "2.6.2",
"@edx/frontend-component-footer": "^12.0.0",
"@edx/frontend-component-header": "^4.0.0",
"@edx/frontend-platform": "^4.2.0",
"@edx/openedx-atlas": "^0.5.0",
"@edx/paragon": "^20.20.0",
"@edx/tinymce-language-selector": "1.1.0",
"@fortawesome/fontawesome-svg-core": "1.2.36",
@@ -53,6 +54,7 @@
"prop-types": "15.8.1",
"react": "16.14.0",
"react-dom": "16.14.0",
"react-helmet": "^6.1.0",
"react-redux": "7.2.9",
"react-router": "5.3.4",
"react-router-dom": "5.3.4",
@@ -61,8 +63,8 @@
"tinymce": "5.10.7"
},
"devDependencies": {
"@edx/browserslist-config": "^1.1.0",
"@edx/frontend-build": "11.0.2",
"@edx/browserslist-config": "^1.2.0",
"@edx/frontend-build": "^12.7.0",
"@edx/reactifex": "^2.1.1",
"@testing-library/jest-dom": "5.16.5",
"@testing-library/react": "12.1.5",

View File

@@ -22,7 +22,7 @@ export default function BulkEmailTool() {
<NavigationTabs courseId={courseId} tabData={courseMetadata.tabs} />
<BulkEmailProvider>
<Container size="md">
<BackToInstructor />
<BackToInstructor courseId={courseId} />
<div className="row pb-4.5">
<h1 className="text-primary-500" id="main-content">
<FormattedMessage

View File

@@ -1,3 +1,5 @@
/* eslint-disable react/jsx-no-constructed-context-values */
import React from 'react';
import PropTypes from 'prop-types';
import useAsyncReducer, { combineReducers } from '../../../utils/useAsyncReducer';

View File

@@ -1,3 +1,4 @@
/* eslint-disable react/no-unstable-nested-components */
import React, { useContext, useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import {

View File

@@ -9,6 +9,8 @@ function ScheduleEmailForm(props) {
const isMobile = useMobileResponsive();
const { isValid, onDateTimeChange, dateTime } = props;
const { date, time } = dateTime;
const descriptionDate = new Date();
descriptionDate.setDate(new Date().getDate() + 1);
return (
<Form.Group>
<div className={classNames('d-flex', isMobile ? 'flex-column' : 'flex-row', 'my-3')}>
@@ -30,7 +32,10 @@ function ScheduleEmailForm(props) {
<small className="text-gray-500 x-small">
<FormattedMessage
id="bulk.email.form.schedule.date.description"
defaultMessage="Enter a start date, e.g. 11/27/2023"
defaultMessage="Enter a start date, e.g. {date}"
values={{
date: descriptionDate.toLocaleDateString(),
}}
/>
</small>
</div>
@@ -52,7 +57,10 @@ function ScheduleEmailForm(props) {
<small className="text-gray-500 x-small">
<FormattedMessage
id="bulk.email.form.schedule.time.description"
defaultMessage="Enter a start time, e.g. 09:00 AM"
defaultMessage="Enter a start time, e.g. {time}"
values={{
time: descriptionDate.toLocaleTimeString([], { timeStyle: 'short' }),
}}
/>
</small>
</div>

View File

@@ -1 +1,2 @@
// eslint-disable-next-line no-restricted-exports
export { default } from './BulkEmailRecipient';

View File

@@ -1 +1,2 @@
// eslint-disable-next-line no-restricted-exports
export { default } from './BulkEmailForm';

View File

@@ -1,3 +1,5 @@
/* eslint-disable react/no-unstable-nested-components */
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { useParams } from 'react-router-dom';
@@ -131,6 +133,7 @@ function BulkEmailContentHistory({ intl }) {
styling="card"
title={intl.formatMessage(messages.emailHistoryTableSectionButton)}
className="mb-3"
// eslint-disable-next-line react/jsx-no-bind
onOpen={fetchSentEmailHistoryData}
>
{showHistoricalEmailContentTable ? (

View File

@@ -71,12 +71,12 @@ export default function BulkEmailTaskManagerTable(props) {
BulkEmailTaskManagerTable.propTypes = {
errorRetrievingData: PropTypes.bool.isRequired,
tableData: PropTypes.arrayOf(PropTypes.object),
tableData: PropTypes.arrayOf(PropTypes.shape({})),
tableDescription: PropTypes.string,
alertWarningMessage: PropTypes.string.isRequired,
alertErrorMessage: PropTypes.string.isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
additionalColumns: PropTypes.arrayOf(PropTypes.object),
columns: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
additionalColumns: PropTypes.arrayOf(PropTypes.shape({})),
};
BulkEmailTaskManagerTable.defaultProps = {

View File

@@ -1,34 +1,40 @@
import React from 'react';
import PropTypes from 'prop-types';
import { getConfig } from '@edx/frontend-platform';
import { Hyperlink, Alert } from '@edx/paragon';
import { WarningFilled } from '@edx/paragon/icons';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
export default function BulkEmailPendingTasksAlert() {
export default function BulkEmailPendingTasksAlert(props) {
const { courseId } = props;
return (
<>
<Alert variant="warning" icon={WarningFilled}>
<Alert variant="warning" icon={WarningFilled}>
<FormattedMessage
id="bulk.email.pending.tasks.description.one"
defaultMessage="To view all pending tasks, including email, visit&nbsp;"
/>
<Hyperlink
destination={`${getConfig().LMS_BASE_URL}/courses/${courseId}/instructor#view-course-info`}
target="_blank"
isInline
showLaunchIcon={false}
>
<FormattedMessage
id="bulk.email.pending.tasks.description.one"
defaultMessage="To view all pending tasks, including email, visit&nbsp;"
id="bulk.email.pending.tasks.link"
defaultMessage="Course Info"
/>
<Hyperlink
destination={`${getConfig().LMS_BASE_URL}/courses/${window.location.pathname.split('/')[2]}/instructor#view-course-info`}
target="_blank"
isInline
showLaunchIcon={false}
>
<FormattedMessage
id="bulk.email.pending.tasks.link"
defaultMessage="Course Info"
/>
</Hyperlink>
<FormattedMessage
id="bulk.email.pending.tasks.description.two"
defaultMessage="&nbsp;in the Instructor Dashboard."
/>
</Alert>
</>
</Hyperlink>
<FormattedMessage
id="bulk.email.pending.tasks.description.two"
defaultMessage="&nbsp;in the Instructor Dashboard."
/>
</Alert>
);
}
BulkEmailPendingTasksAlert.propTypes = {
courseId: PropTypes.string.isRequired,
};

View File

@@ -89,6 +89,7 @@ function BulkEmailTaskHistory({ intl }) {
<Collapsible
styling="card"
title={intl.formatMessage(messages.emailTaskHistoryTableSectionButton)}
// eslint-disable-next-line react/jsx-no-bind
onOpen={fetchEmailTaskHistoryData}
>
{showHistoricalTaskContentTable ? (

View File

@@ -1,4 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { getConfig } from '@edx/frontend-platform';
@@ -8,7 +9,7 @@ import messages from './messages';
import BulkEmailScheduledEmailsTable from './bulk-email-scheduled-emails-table';
import BulkEmailPendingTasksAlert from './BulkEmailPendingTasksAlert';
function BulkEmailTaskManager({ intl }) {
function BulkEmailTaskManager({ intl, courseId }) {
return (
<div className="w-100">
{getConfig().SCHEDULE_EMAIL_SECTION && (
@@ -26,7 +27,7 @@ function BulkEmailTaskManager({ intl }) {
</div>
<div className="border-top border-primary-500 pt-4.5">
<h2 className="h3 mb-4 text-primary-500">{intl.formatMessage(messages.pendingTasksHeader)}</h2>
<BulkEmailPendingTasksAlert />
<BulkEmailPendingTasksAlert courseId={courseId} />
</div>
</div>
);
@@ -34,6 +35,7 @@ function BulkEmailTaskManager({ intl }) {
BulkEmailTaskManager.propTypes = {
intl: intlShape.isRequired,
courseId: PropTypes.string.isRequired,
};
export default injectIntl(BulkEmailTaskManager);

View File

@@ -1,4 +1,6 @@
/* eslint-disable react/prop-types */
/* eslint-disable react/no-unstable-nested-components */
import React, {
useCallback, useContext, useState, useEffect,
} from 'react';
@@ -24,6 +26,7 @@ function flattenScheduledEmailsArray(emails) {
emailId: email.courseEmail.id,
task: email.task,
taskDue: new Date(email.taskDue).toLocaleString(),
taskDueUTC: email.taskDue,
...email.courseEmail,
targets: email.courseEmail.targets.join(', '),
}));
@@ -89,10 +92,10 @@ function BulkEmailScheduledEmailsTable({ intl }) {
const handleEditEmail = (row) => {
const {
original: {
htmlMessage: emailBody, subject: emailSubject, taskDue, targets, schedulingId, emailId,
htmlMessage: emailBody, subject: emailSubject, taskDueUTC, targets, schedulingId, emailId,
},
} = row;
const dateTime = new Date(taskDue);
const dateTime = new Date(taskDueUTC);
const emailRecipients = targets.replaceAll('-', ':').split(', ');
const scheduleDate = formatDate(dateTime);
const scheduleTime = formatTime(dateTime);

View File

@@ -1 +1,2 @@
// eslint-disable-next-line no-restricted-exports
export { default } from './BulkEmailScheduledEmailsTable';

View File

@@ -0,0 +1,33 @@
import React from 'react';
import BulkEmailPendingTasksAlert from '../BulkEmailPendingTasksAlert';
import {
initializeMockApp, render, screen,
} from '../../../../setupTest';
describe('Testing BulkEmailPendingTasksAlert Component', () => {
beforeAll(async () => {
await initializeMockApp();
});
test('Render without Public path', async () => {
render(<BulkEmailPendingTasksAlert courseId="test-course-id" />);
const linkEl = await screen.findByText('Course Info');
expect(linkEl.href).toEqual('http://localhost:18000/courses/test-course-id/instructor#view-course-info');
});
test('Render with Public path', async () => {
Object.defineProperty(window, 'location', {
get() {
return { pathname: '/communications/courses/test-course-id/bulk-email' };
},
});
render(<BulkEmailPendingTasksAlert courseId="test-course-id" />);
const linkEl = await screen.findByText('Course Info');
expect(linkEl.href).toEqual('http://localhost:18000/courses/test-course-id/instructor#view-course-info');
expect(window.location.pathname).toEqual('/communications/courses/test-course-id/bulk-email');
});
});

View File

@@ -1 +1,2 @@
// eslint-disable-next-line no-restricted-exports
export { default } from './BulkEmailTool';

View File

@@ -40,6 +40,7 @@ function TaskAlertModal(props) {
// causing strange click event target issues in safari. To solve this, we want to
// wrap the string in a fragment instead of a span, so that the whole button considered
// a "button" target, and not a "span inside a button"
// eslint-disable-next-line react/jsx-no-useless-fragment
msg => <>{msg}</>
}
</FormattedMessage>

View File

@@ -1 +1,2 @@
// eslint-disable-next-line no-restricted-exports
export { default } from './TaskAlertModal';

View File

@@ -43,6 +43,8 @@ export default function TextEditor(props) {
block_unsupported_drop: false,
image_advtab: true,
name: 'emailBody',
relative_urls: false,
remove_script_host: false,
}}
onEditorChange={onChange}
value={value}

View File

@@ -1 +1,2 @@
// eslint-disable-next-line no-restricted-exports
export { default } from './TextEditor';

View File

@@ -1,16 +1,19 @@
import React from 'react';
import PropTypes from 'prop-types';
import { getConfig } from '@edx/frontend-platform';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { Button, Icon } from '@edx/paragon';
import { ArrowBack } from '@edx/paragon/icons';
export default function BackToInstructor() {
export default function BackToInstructor(props) {
const { courseId } = props;
return (
<Button
variant="tertiary"
className="mb-4.5 ml-n4.5 text-primary-500"
href={`${getConfig().LMS_BASE_URL}/courses/${window.location.pathname.split('/')[2]}/instructor#view-course-info`}
href={`${getConfig().LMS_BASE_URL}/courses/${courseId}/instructor#view-course-info`}
>
<Icon
src={ArrowBack}
@@ -24,3 +27,7 @@ export default function BackToInstructor() {
</Button>
);
}
BackToInstructor.propTypes = {
courseId: PropTypes.string.isRequired,
};

View File

@@ -0,0 +1,33 @@
import React from 'react';
import BackToInstructor from './BackToInstructor';
import {
initializeMockApp, render, screen,
} from '../../setupTest';
describe('Testing BackToInstructor Component', () => {
beforeAll(async () => {
await initializeMockApp();
});
test('Render without Public path', async () => {
render(<BackToInstructor courseId="test-course-id" />);
const linkEl = await screen.findByText('Back to Instructor Dashboard');
expect(linkEl.href).toEqual('http://localhost:18000/courses/test-course-id/instructor#view-course-info');
});
test('Render with Public path', async () => {
Object.defineProperty(window, 'location', {
get() {
return { pathname: '/communications/courses/test-course-id/bulk-email' };
},
});
render(<BackToInstructor courseId="test-course-id" />);
const linkEl = await screen.findByText('Back to Instructor Dashboard');
expect(linkEl.href).toEqual('http://localhost:18000/courses/test-course-id/instructor#view-course-info');
expect(window.location.pathname).toEqual('/communications/courses/test-course-id/bulk-email');
});
});

View File

@@ -59,18 +59,18 @@ export default function PageContainer(props) {
if (courseMetadata) {
return (
<CourseMetadataContext.Provider value={courseMetadata}>
<>
<Header
className="learning-header"
courseOrg={courseMetadata.org}
courseNumber={courseMetadata.number}
courseTitle={courseMetadata.title}
/>
<Header
className="learning-header"
courseOrg={courseMetadata.org}
courseNumber={courseMetadata.number}
courseTitle={courseMetadata.title}
/>
<div className="pb-3 container">
<main>
{children}
</main>
<Footer />
</>
</div>
<Footer />
</CourseMetadataContext.Provider>
);
}

View File

@@ -1,10 +1,10 @@
import { camelCaseObject, getConfig } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
const courseHomeBaseUrl = `${getConfig().LMS_BASE_URL}/api/course_home/v1/course_metadata`;
export const getCourseHomeBaseUrl = () => `${getConfig().LMS_BASE_URL}/api/course_home/v1/course_metadata`;
export async function getCourseHomeCourseMetadata(courseId) {
const courseHomeMetadataUrl = `${courseHomeBaseUrl}/${courseId}`;
const courseHomeMetadataUrl = `${getCourseHomeBaseUrl()}/${courseId}`;
const { data } = await getAuthenticatedHttpClient().get(courseHomeMetadataUrl);
return camelCaseObject(data);
}

View File

@@ -0,0 +1,24 @@
import { Factory } from 'rosie'; // eslint-disable-line import/no-extraneous-dependencies
import { camelCaseObject } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import MockAdapter from 'axios-mock-adapter';
import { initializeMockApp } from '../../../setupTest';
import * as api from './api';
import './__factories__/courseMetadata.factory';
describe('api', () => {
beforeAll(async () => {
await initializeMockApp();
});
test('getCourseHomeCourseMetadata', async () => {
const axiosMock = new MockAdapter(getAuthenticatedHttpClient());
const courseMetadata = Factory.build('courseMetadata');
const { id: courseId } = courseMetadata;
axiosMock
.onGet(`${api.getCourseHomeBaseUrl()}/${courseId}`)
.reply(200, courseMetadata);
const data = await api.getCourseHomeCourseMetadata(courseId);
expect(data).toEqual(camelCaseObject(courseMetadata));
});
});

View File

@@ -1,3 +1,7 @@
import { messages as footerMessages } from '@edx/frontend-component-footer';
import { messages as headerMessages } from '@edx/frontend-component-header';
import { messages as paragonMessages } from '@edx/paragon';
import arMessages from './messages/ar.json';
import caMessages from './messages/ca.json';
// no need to import en messages-- they are in the defaultMessage field
@@ -13,7 +17,7 @@ import ruMessages from './messages/ru.json';
import thMessages from './messages/th.json';
import ukMessages from './messages/uk.json';
const messages = {
const appMessages = {
ar: arMessages,
'es-419': es419Messages,
fr: frMessages,
@@ -29,4 +33,9 @@ const messages = {
uk: ukMessages,
};
export default messages;
export default [
headerMessages,
footerMessages,
paragonMessages,
appMessages,
];

View File

@@ -2,16 +2,14 @@ import 'core-js/stable';
import 'regenerator-runtime/runtime';
import {
APP_INIT_ERROR, APP_READY, subscribe, initialize, mergeConfig,
APP_INIT_ERROR, APP_READY, subscribe, initialize, mergeConfig, getConfig,
} from '@edx/frontend-platform';
import { AppProvider, AuthenticatedPageRoute, ErrorPage } from '@edx/frontend-platform/react';
import ReactDOM from 'react-dom';
import { messages as headerMessages } from '@edx/frontend-component-header';
import { messages as footerMessages } from '@edx/frontend-component-footer';
import { messages as paragonMessages } from '@edx/paragon';
import { Helmet } from 'react-helmet';
import { Switch } from 'react-router-dom';
import appMessages from './i18n';
import messages from './i18n';
import './index.scss';
import BulkEmailTool from './components/bulk-email-tool';
@@ -20,15 +18,16 @@ import PageContainer from './components/page-container/PageContainer';
subscribe(APP_READY, () => {
ReactDOM.render(
<AppProvider>
<div className="pb-3 container">
<Switch>
<AuthenticatedPageRoute path="/courses/:courseId/bulk_email">
<PageContainer>
<BulkEmailTool />
</PageContainer>
</AuthenticatedPageRoute>
</Switch>
</div>
<Helmet>
<link rel="shortcut icon" href={getConfig().FAVICON_URL} type="image/x-icon" />
</Helmet>
<Switch>
<AuthenticatedPageRoute path="/courses/:courseId/bulk_email">
<PageContainer>
<BulkEmailTool />
</PageContainer>
</AuthenticatedPageRoute>
</Switch>
</AppProvider>,
document.getElementById('root'),
);
@@ -50,5 +49,5 @@ initialize({
);
},
},
messages: [appMessages, headerMessages, footerMessages, paragonMessages],
messages,
});

View File

@@ -8,8 +8,12 @@ import { configure as configureI18n, IntlProvider } from '@edx/frontend-platform
import { configure as configureLogging, MockLoggingService } from '@edx/frontend-platform/logging';
import { getConfig, mergeConfig } from '@edx/frontend-platform';
import { configure as configureAuth, MockAuthService } from '@edx/frontend-platform/auth';
import appMessages from './i18n';
import messages from './i18n';
jest.mock('@edx/frontend-platform/react/hooks', () => ({
...jest.requireActual('@edx/frontend-platform/react/hooks'),
useTrackColorSchemeChoice: jest.fn(),
}));
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation((query) => ({
@@ -43,13 +47,18 @@ export function initializeMockApp() {
const i18nService = configureI18n({
config: getConfig(),
loggingService,
messages: [appMessages],
messages,
});
const authService = configureAuth(MockAuthService, { config: getConfig(), loggingService });
return { loggingService, i18nService, authService };
}
jest.mock('@edx/frontend-platform/react/hooks', () => ({
...jest.requireActual('@edx/frontend-platform/react/hooks'),
useTrackColorSchemeChoice: jest.fn(),
}));
function render(ui, options) {
// eslint-disable-next-line react/prop-types
function Wrapper({ children }) {