+
+
{
if (updateSettingsRequestStatus === RequestStatus.SUCCESSFUL) {
@@ -102,7 +102,7 @@ function AppSettingsModal({
isOpen
closeText={intl.formatMessage(messages.cancel)}
dialogClassName="modal-dialog-centered modal-lg"
- hasCloseButton={isTabletOrMobile}
+ hasCloseButton={isMobile}
onClose={onClose}
variant={modalVariant}
isFullscreenOnMobile
diff --git a/src/pages-and-resources/discussions/app-config-form/apps/legacy/LegacyConfigForm.jsx b/src/pages-and-resources/discussions/app-config-form/apps/legacy/LegacyConfigForm.jsx
index ab89ffe66..7451c9379 100644
--- a/src/pages-and-resources/discussions/app-config-form/apps/legacy/LegacyConfigForm.jsx
+++ b/src/pages-and-resources/discussions/app-config-form/apps/legacy/LegacyConfigForm.jsx
@@ -1,6 +1,7 @@
-import React, { useState } from 'react';
+import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { Card, Form } from '@edx/paragon';
+import { useDispatch } from 'react-redux';
import { Formik } from 'formik';
import * as Yup from 'yup';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
@@ -10,6 +11,7 @@ import AnonymousPostingFields from '../shared/AnonymousPostingFields';
import DiscussionTopics from '../shared/discussion-topics/DiscussionTopics';
import BlackoutDatesField, { blackoutDatesRegex } from '../shared/BlackoutDatesField';
import LegacyConfigFormProvider from './LegacyConfigFormProvider';
+import { updateValidationStatus } from '../../../data/slice';
import messages from '../shared/messages';
import AppConfigFormDivider from '../shared/AppConfigFormDivider';
@@ -37,6 +39,13 @@ Yup.addMethod(Yup.object, 'uniqueProperty', function (propertyName, message) {
function LegacyConfigForm({
appConfig, onSubmit, formRef, intl, title,
}) {
+ const [isFormInvalid, setIsFormInvalid] = useState(false);
+ const dispatch = useDispatch();
+
+ useEffect(() => {
+ dispatch(updateValidationStatus({ hasError: isFormInvalid }));
+ }, [isFormInvalid]);
+
const [validDiscussionTopics, setValidDiscussionTopics] = useState(appConfig.discussionTopics);
const legacyFormValidationSchema = Yup.object().shape({
blackoutDates: Yup.string().matches(
@@ -79,6 +88,8 @@ function LegacyConfigForm({
setValidDiscussionTopics,
discussionTopicErrors,
};
+ setIsFormInvalid(discussionTopicErrors.some((error) => error === true)
+ || Boolean(touched.blackoutDates && errors.blackoutDates));
return (
@@ -96,12 +107,7 @@ function LegacyConfigForm({
-
+
diff --git a/src/pages-and-resources/discussions/app-config-form/apps/shared/BlackoutDatesField.jsx b/src/pages-and-resources/discussions/app-config-form/apps/shared/BlackoutDatesField.jsx
index 0e39ea84f..b18e16141 100644
--- a/src/pages-and-resources/discussions/app-config-form/apps/shared/BlackoutDatesField.jsx
+++ b/src/pages-and-resources/discussions/app-config-form/apps/shared/BlackoutDatesField.jsx
@@ -1,7 +1,7 @@
-import React from 'react';
-import PropTypes from 'prop-types';
+import React, { useState } from 'react';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
-import { Form } from '@edx/paragon';
+import { Form, TransitionReplace } from '@edx/paragon';
+import { useFormikContext } from 'formik';
import messages from './messages';
/**
@@ -68,49 +68,57 @@ import messages from './messages';
*/
export const blackoutDatesRegex = /^\[(\[("[0-9]{4}-(0[1-9]|1[0-2])-[0-3][0-9](T([0-1][0-9]|2[0-3]):([0-5][0-9])){0,1}"),("[0-9]{4}-(0[1-9]|1[0-2])-[0-3][0-9](T([0-1][0-9]|2[0-3]):([0-5][0-9])){0,1}")\](,){0,1})*\]$/;
-function BlackoutDatesField({
- onBlur,
- onChange,
- intl,
- values,
- errors,
-}) {
+const BlackoutDatesField = ({ intl }) => {
+ const [inFocus, setInFocus] = useState(false);
+ const {
+ handleChange, handleBlur, errors,
+ touched, values: appConfig,
+ } = useFormikContext();
+
+ const hasError = Boolean(touched.blackoutDates && errors.blackoutDates);
+
+ const handleFocusOut = (event) => {
+ handleBlur(event);
+ setInFocus(false);
+ };
+
return (
<>
handleFocusOut(event)}
+ className="mb-1"
floatingLabel={intl.formatMessage(messages.blackoutDatesLabel)}
+ onFocus={() => setInFocus(true)}
/>
- {errors.blackoutDates && (
-
- {errors.blackoutDates}
-
- )}
-
+
+ {hasError && !inFocus ? (
+
+
+
+
+ ) : (
+
+ )}
+
+
{intl.formatMessage(messages.blackoutDatesHelp)}
>
);
-}
+};
BlackoutDatesField.propTypes = {
- onBlur: PropTypes.func.isRequired,
- onChange: PropTypes.func.isRequired,
intl: intlShape.isRequired,
- values: PropTypes.shape({
- blackoutDates: PropTypes.string,
- }).isRequired,
- errors: PropTypes.shape({
- blackoutDates: PropTypes.string,
- }).isRequired,
};
export default injectIntl(BlackoutDatesField);
diff --git a/src/pages-and-resources/discussions/app-config-form/apps/shared/discussion-topics/DiscussionTopics.jsx b/src/pages-and-resources/discussions/app-config-form/apps/shared/discussion-topics/DiscussionTopics.jsx
index 06dfb08ed..a17337f7b 100644
--- a/src/pages-and-resources/discussions/app-config-form/apps/shared/discussion-topics/DiscussionTopics.jsx
+++ b/src/pages-and-resources/discussions/app-config-form/apps/shared/discussion-topics/DiscussionTopics.jsx
@@ -1,5 +1,4 @@
-import React, { useEffect, useContext, useCallback } from 'react';
-import { useDispatch } from 'react-redux';
+import React, { useContext, useCallback } from 'react';
import { Add } from '@edx/paragon/icons';
import { Button } from '@edx/paragon';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
@@ -8,7 +7,6 @@ import { v4 as uuid } from 'uuid';
import _ from 'lodash';
import messages from '../messages';
import TopicItem from './TopicItem';
-import { updateValidationStatus } from '../../../../data/slice';
import { LegacyConfigFormContext } from '../../legacy/LegacyConfigFormProvider';
import filterItemFromObject from '../../../utils';
@@ -19,18 +17,12 @@ const DiscussionTopics = ({ intl }) => {
setFieldValue,
} = useFormikContext();
const { discussionTopics, divideDiscussionIds } = appConfig;
- const dispatch = useDispatch();
const {
discussionTopicErrors,
validDiscussionTopics,
setValidDiscussionTopics,
} = useContext(LegacyConfigFormContext);
- const isFormInvalid = discussionTopicErrors.some((error) => error === true);
- useEffect(() => {
- dispatch(updateValidationStatus({ hasError: isFormInvalid }));
- }, [isFormInvalid]);
-
const handleTopicDelete = async (topicIndex, topicId, remove) => {
await remove(topicIndex);
validateForm();
diff --git a/src/pages-and-resources/discussions/app-list/AppCard.jsx b/src/pages-and-resources/discussions/app-list/AppCard.jsx
index c93a79b98..2a92c5732 100644
--- a/src/pages-and-resources/discussions/app-list/AppCard.jsx
+++ b/src/pages-and-resources/discussions/app-list/AppCard.jsx
@@ -45,7 +45,7 @@ function AppCard({
})}
/>
-
+
event.stopPropagation()}
title={(
<>
@@ -32,7 +33,7 @@ function FeaturesList({ app, features, intl }) {
styling="basic"
>
{features && features.map((feature) => (
-
{intl.formatMessage(messages.heading)}
{intl.formatMessage(messages.blackoutDates)}
{errors.blackoutDates}
+
{intl.formatMessage(messages[`appName-${app.id}`])}
diff --git a/src/pages-and-resources/discussions/app-list/FeaturesList.jsx b/src/pages-and-resources/discussions/app-list/FeaturesList.jsx
index 412732637..9f2f7b932 100644
--- a/src/pages-and-resources/discussions/app-list/FeaturesList.jsx
+++ b/src/pages-and-resources/discussions/app-list/FeaturesList.jsx
@@ -19,6 +19,7 @@ const NonSupportedFeature = (
function FeaturesList({ app, features, intl }) {
return (
+
{app.featureIds.includes(feature.id)
? SupportedFeature
: NonSupportedFeature}
diff --git a/src/pages-and-resources/discussions/app-list/FeaturesTable.jsx b/src/pages-and-resources/discussions/app-list/FeaturesTable.jsx
index f2731ef43..cc14d3a15 100644
--- a/src/pages-and-resources/discussions/app-list/FeaturesTable.jsx
+++ b/src/pages-and-resources/discussions/app-list/FeaturesTable.jsx
@@ -4,6 +4,7 @@ import { Remove, Check } from '@edx/paragon/icons';
import { DataTable } from '@edx/paragon';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import messages from './messages';
+import './FeaturesTable.scss';
function FeaturesTable({ apps, features, intl }) {
return (
diff --git a/src/pages-and-resources/discussions/app-list/FeaturesTable.scss b/src/pages-and-resources/discussions/app-list/FeaturesTable.scss
new file mode 100644
index 000000000..5f3417e12
--- /dev/null
+++ b/src/pages-and-resources/discussions/app-list/FeaturesTable.scss
@@ -0,0 +1,4 @@
+table {
+ font-size: 14px;
+ color: black;
+}
diff --git a/src/pages-and-resources/pages/PageCard.jsx b/src/pages-and-resources/pages/PageCard.jsx
index b0f5c8a13..c5992b28e 100644
--- a/src/pages-and-resources/pages/PageCard.jsx
+++ b/src/pages-and-resources/pages/PageCard.jsx
@@ -1,4 +1,5 @@
import { history } from '@edx/frontend-platform';
+import classNames from 'classnames';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import {
Badge, Card, Icon, IconButton, Hyperlink,
@@ -8,6 +9,8 @@ import PropTypes from 'prop-types';
import React, { useContext } from 'react';
import messages from '../messages';
import { PagesAndResourcesContext } from '../PagesAndResourcesProvider';
+import { useIsDesktop } from '../../utils';
+import './PageCard.scss';
const CoursePageShape = PropTypes.shape({
id: PropTypes.string.isRequired,
@@ -27,6 +30,7 @@ function PageCard({
page,
}) {
const { path: pagesAndResourcesPath } = useContext(PagesAndResourcesContext);
+ const isDesktop = useIsDesktop();
const SettingsButton = () => {
if (page.legacyLink) {
@@ -55,11 +59,13 @@ function PageCard({
return (
diff --git a/src/pages-and-resources/pages/PageCard.scss b/src/pages-and-resources/pages/PageCard.scss
new file mode 100644
index 000000000..457fbc7c7
--- /dev/null
+++ b/src/pages-and-resources/pages/PageCard.scss
@@ -0,0 +1,9 @@
+.desktop-card {
+ width: 19rem;
+ height: 14rem;
+}
+
+.mobile-card {
+ width: 100%;
+ height: 14rem;
+}
diff --git a/src/utils.js b/src/utils.js
index 35c607e16..24aa4a05b 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -6,5 +6,9 @@ export const executeThunk = async (thunk, dispatch, getState) => {
};
export function useIsMobile() {
- return useMediaQuery({ query: '(max-width: 768px)' });
+ return useMediaQuery({ query: '(max-width: 767.98px)' });
+}
+
+export function useIsDesktop() {
+ return useMediaQuery({ query: '(min-width: 992px)' });
}