From 2bd8037d7b76cc118e476178d06dd9b9e110b7a1 Mon Sep 17 00:00:00 2001 From: Kristin Aoki <42981026+KristinAoki@users.noreply.github.com> Date: Wed, 6 Sep 2023 11:02:16 -0400 Subject: [PATCH] feat: change head title depending on page (#582) --- src/advanced-settings/AdvancedSettings.jsx | 5 +++++ src/course-team/CourseTeam.jsx | 5 +++++ src/course-updates/CourseUpdates.jsx | 5 +++++ src/custom-pages/CustomPages.jsx | 6 +++++- src/files-and-uploads/FilesAndUploads.jsx | 6 +++++- src/generic/utils.js | 10 ++++++++++ src/generic/utils.test.js | 16 ++++++++++++++++ src/grading-settings/GradingSettings.jsx | 5 +++++ src/pages-and-resources/PagesAndResources.jsx | 4 ++++ .../ProctoredExamSettings.jsx | 7 ++++++- src/schedule-and-details/index.jsx | 5 +++++ 11 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 src/generic/utils.js create mode 100644 src/generic/utils.test.js diff --git a/src/advanced-settings/AdvancedSettings.jsx b/src/advanced-settings/AdvancedSettings.jsx index 9b9f8b720..ea6f0e9a8 100644 --- a/src/advanced-settings/AdvancedSettings.jsx +++ b/src/advanced-settings/AdvancedSettings.jsx @@ -9,6 +9,7 @@ import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/ import Placeholder from '@edx/frontend-lib-content-components'; import AlertProctoringError from '../generic/AlertProctoringError'; +import { useModel } from '../generic/model-store'; import InternetConnectionAlert from '../generic/internet-connection-alert'; import { parseArrayOrObjectValues } from '../utils'; import { RequestStatus } from '../data/constants'; @@ -23,6 +24,7 @@ import SettingsSidebar from './settings-sidebar/SettingsSidebar'; import validateAdvancedSettingsData from './utils'; import messages from './messages'; import ModalError from './modal-error/ModalError'; +import getPageHeadTitle from '../generic/utils'; const AdvancedSettings = ({ intl, courseId }) => { const dispatch = useDispatch(); @@ -36,6 +38,9 @@ const AdvancedSettings = ({ intl, courseId }) => { const [isEditableState, setIsEditableState] = useState(false); const [hasInternetConnectionError, setInternetConnectionError] = useState(false); + const courseDetails = useModel('courseDetails', courseId); + document.title = getPageHeadTitle(courseDetails?.name, intl.formatMessage(messages.headingTitle)); + useEffect(() => { dispatch(fetchCourseAppSettings(courseId)); dispatch(fetchProctoringExamErrors(courseId)); diff --git a/src/course-team/CourseTeam.jsx b/src/course-team/CourseTeam.jsx index d0a2ba573..e5a3c1b84 100644 --- a/src/course-team/CourseTeam.jsx +++ b/src/course-team/CourseTeam.jsx @@ -9,6 +9,7 @@ import { import { Add as IconAdd } from '@edx/paragon/icons'; import InternetConnectionAlert from '../generic/internet-connection-alert'; +import { useModel } from '../generic/model-store'; import SubHeader from '../generic/sub-header/SubHeader'; import { USER_ROLES } from '../constants'; import messages from './messages'; @@ -18,10 +19,14 @@ import AddTeamMember from './add-team-member/AddTeamMember'; import CourseTeamMember from './course-team-member/CourseTeamMember'; import InfoModal from './info-modal/InfoModal'; import { useCourseTeam } from './hooks'; +import getPageHeadTitle from '../generic/utils'; const CourseTeam = ({ courseId }) => { const intl = useIntl(); + const courseDetails = useModel('courseDetails', courseId); + document.title = getPageHeadTitle(courseDetails?.name, intl.formatMessage(messages.headingTitle)); + const { modalType, errorMessage, diff --git a/src/course-updates/CourseUpdates.jsx b/src/course-updates/CourseUpdates.jsx index cfcdfea96..416778526 100644 --- a/src/course-updates/CourseUpdates.jsx +++ b/src/course-updates/CourseUpdates.jsx @@ -9,6 +9,7 @@ import { import { Add as AddIcon } from '@edx/paragon/icons'; import { useSelector } from 'react-redux'; +import { useModel } from '../generic/model-store'; import { getProcessingNotification } from '../generic/processing-notification/data/selectors'; import ProcessingNotification from '../generic/processing-notification'; import SubHeader from '../generic/sub-header/SubHeader'; @@ -23,10 +24,14 @@ import messages from './messages'; import { useCourseUpdates } from './hooks'; import { getLoadingStatuses, getSavingStatuses } from './data/selectors'; import { matchesAnyStatus } from './utils'; +import getPageHeadTitle from '../generic/utils'; const CourseUpdates = ({ courseId }) => { const intl = useIntl(); + const courseDetails = useModel('courseDetails', courseId); + document.title = getPageHeadTitle(courseDetails?.name, intl.formatMessage(messages.headingTitle)); + const { requestType, courseUpdates, diff --git a/src/custom-pages/CustomPages.jsx b/src/custom-pages/CustomPages.jsx index b7019ba10..1f2955ffc 100644 --- a/src/custom-pages/CustomPages.jsx +++ b/src/custom-pages/CustomPages.jsx @@ -26,7 +26,7 @@ import Placeholder, { } from '@edx/frontend-lib-content-components'; import { RequestStatus } from '../data/constants'; -import { useModels } from '../generic/model-store'; +import { useModels, useModel } from '../generic/model-store'; import { getLoadingStatus, getSavingStatus } from './data/selectors'; import { addSingleCustomPage, @@ -40,6 +40,7 @@ import CustomPageCard from './CustomPageCard'; import messages from './messages'; import CustomPagesProvider from './CustomPagesProvider'; import EditModal from './EditModal'; +import getPageHeadTitle from '../generic/utils'; const CustomPages = ({ courseId, @@ -52,6 +53,9 @@ const CustomPages = ({ const [isOpen, open, close] = useToggle(false); const [isEditModalOpen, openEditModal, closeEditModal] = useToggle(false); + const courseDetails = useModel('courseDetails', courseId); + document.title = getPageHeadTitle(courseDetails?.name, intl.formatMessage(messages.heading)); + const { config } = useContext(AppContext); const { path, url } = useRouteMatch(); const learningCourseURL = `${config.LEARNING_BASE_URL}/course/${courseId}`; diff --git a/src/files-and-uploads/FilesAndUploads.jsx b/src/files-and-uploads/FilesAndUploads.jsx index 98070e151..d2a203de0 100644 --- a/src/files-and-uploads/FilesAndUploads.jsx +++ b/src/files-and-uploads/FilesAndUploads.jsx @@ -17,7 +17,7 @@ import { import Placeholder, { ErrorAlert } from '@edx/frontend-lib-content-components'; import { RequestStatus } from '../data/constants'; -import { useModels } from '../generic/model-store'; +import { useModels, useModel } from '../generic/model-store'; import { addAssetFile, deleteAssetFile, @@ -41,6 +41,7 @@ import { } from './table-components'; import ApiStatusToast from './ApiStatusToast'; import { clearErrors } from './data/slice'; +import getPageHeadTitle from '../generic/utils'; const FilesAndUploads = ({ courseId, @@ -62,6 +63,9 @@ const FilesAndUploads = ({ const [selectedRows, setSelectedRows] = useState([]); const [isDeleteConfirmationOpen, openDeleteConfirmation, closeDeleteConfirmation] = useToggle(false); + const courseDetails = useModel('courseDetails', courseId); + document.title = getPageHeadTitle(courseDetails?.name, intl.formatMessage(messages.heading)); + useEffect(() => { dispatch(fetchAssets(courseId)); }, [courseId]); diff --git a/src/generic/utils.js b/src/generic/utils.js new file mode 100644 index 000000000..27236e8aa --- /dev/null +++ b/src/generic/utils.js @@ -0,0 +1,10 @@ +import { isEmpty } from 'lodash'; + +const getPageHeadTitle = (courseName, pageName) => { + if (isEmpty(courseName)) { + return `${pageName} | ${process.env.SITE_NAME}`; + } + return `${pageName} | ${courseName} | ${process.env.SITE_NAME}`; +}; + +export default getPageHeadTitle; diff --git a/src/generic/utils.test.js b/src/generic/utils.test.js new file mode 100644 index 000000000..587164d9d --- /dev/null +++ b/src/generic/utils.test.js @@ -0,0 +1,16 @@ +import getPageHeadTitle from './utils'; + +describe('utils', () => { + describe('getPageHeader', () => { + it('should return with page name and site name', () => { + const expected = 'pageName | edX'; + const actual = getPageHeadTitle(null, 'pageName'); + expect(expected).toEqual(actual); + }); + it('should return with page name, course name, and site name', () => { + const expected = 'pageName | courseName | edX'; + const actual = getPageHeadTitle('courseName', 'pageName'); + expect(expected).toEqual(actual); + }); + }); +}); diff --git a/src/grading-settings/GradingSettings.jsx b/src/grading-settings/GradingSettings.jsx index 9df43dc29..ede74d4ff 100644 --- a/src/grading-settings/GradingSettings.jsx +++ b/src/grading-settings/GradingSettings.jsx @@ -7,6 +7,7 @@ import { } from '@edx/paragon'; import { CheckCircle, Warning, Add as IconAdd } from '@edx/paragon/icons'; +import { useModel } from '../generic/model-store'; import AlertMessage from '../generic/alert-message'; import { RequestStatus } from '../data/constants'; import InternetConnectionAlert from '../generic/internet-connection-alert'; @@ -28,6 +29,7 @@ import AssignmentSection from './assignment-section'; import CreditSection from './credit-section'; import DeadlineSection from './deadline-section'; import { useConvertGradeCutoffs, useUpdateGradingData } from './hooks'; +import getPageHeadTitle from '../generic/utils'; const GradingSettings = ({ intl, courseId }) => { const gradingSettingsData = useSelector(getGradingSettings); @@ -42,6 +44,9 @@ const GradingSettings = ({ intl, courseId }) => { const [showOverrideInternetConnectionAlert, setOverrideInternetConnectionAlert] = useState(false); const [eligibleGrade, setEligibleGrade] = useState(null); + const courseDetails = useModel('courseDetails', courseId); + document.title = getPageHeadTitle(courseDetails?.name, intl.formatMessage(messages.headingTitle)); + const { graders, resetDataRef, diff --git a/src/pages-and-resources/PagesAndResources.jsx b/src/pages-and-resources/PagesAndResources.jsx index 3147741cc..d91cbefae 100644 --- a/src/pages-and-resources/PagesAndResources.jsx +++ b/src/pages-and-resources/PagesAndResources.jsx @@ -23,10 +23,14 @@ import { getCourseAppsApiStatus, getLoadingStatus } from './data/selectors'; import PagesAndResourcesProvider from './PagesAndResourcesProvider'; import { RequestStatus } from '../data/constants'; import PermissionDeniedAlert from '../generic/PermissionDeniedAlert'; +import getPageHeadTitle from '../generic/utils'; const PagesAndResources = ({ courseId, intl }) => { const { path, url } = useRouteMatch(); + const courseDetails = useModel('courseDetails', courseId); + document.title = getPageHeadTitle(courseDetails?.name, intl.formatMessage(messages.heading)); + const dispatch = useDispatch(); useEffect(() => { dispatch(fetchCourseApps(courseId)); diff --git a/src/proctored-exam-settings/ProctoredExamSettings.jsx b/src/proctored-exam-settings/ProctoredExamSettings.jsx index 7a4757507..577f32d15 100644 --- a/src/proctored-exam-settings/ProctoredExamSettings.jsx +++ b/src/proctored-exam-settings/ProctoredExamSettings.jsx @@ -12,8 +12,9 @@ import { intlShape, FormattedMessage, } from '@edx/frontend-platform/i18n'; - import { getConfig } from '@edx/frontend-platform'; + +import { useModel } from '../generic/model-store'; import messages from './ProctoredExamSettings.messages'; import ExamsApiService from '../data/services/ExamsApiService'; import StudioApiService from '../data/services/StudioApiService'; @@ -25,6 +26,7 @@ import { fetchExamSettingsPending, fetchExamSettingsSuccess, } from './data/thunks'; +import getPageHeadTitle from '../generic/utils'; const ProctoredExamSettings = ({ courseId, intl }) => { const dispatch = useDispatch(); @@ -51,6 +53,9 @@ const ProctoredExamSettings = ({ courseId, intl }) => { errors: {}, }); + const courseDetails = useModel('courseDetails', courseId); + document.title = getPageHeadTitle(courseDetails?.name, 'Proctored Exam Settings'); + const alertRef = React.createRef(); const saveStatusAlertRef = React.createRef(); const proctoringEscalationEmailInputRef = useRef(null); diff --git a/src/schedule-and-details/index.jsx b/src/schedule-and-details/index.jsx index 11d110230..153344256 100644 --- a/src/schedule-and-details/index.jsx +++ b/src/schedule-and-details/index.jsx @@ -12,6 +12,7 @@ import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import Placeholder from '@edx/frontend-lib-content-components'; import { RequestStatus } from '../data/constants'; +import { useModel } from '../generic/model-store'; import AlertMessage from '../generic/alert-message'; import InternetConnectionAlert from '../generic/internet-connection-alert'; import { STATEFUL_BUTTON_STATES } from '../constants'; @@ -39,6 +40,7 @@ import LicenseSection from './license-section'; import ScheduleSidebar from './schedule-sidebar'; import messages from './messages'; import { useSaveValuesPrompt } from './hooks'; +import getPageHeadTitle from '../generic/utils'; const ScheduleAndDetails = ({ intl, courseId }) => { const courseSettings = useSelector(getCourseSettings); @@ -48,6 +50,9 @@ const ScheduleAndDetails = ({ intl, courseId }) => { const isLoading = loadingDetailsStatus === RequestStatus.IN_PROGRESS || loadingSettingsStatus === RequestStatus.IN_PROGRESS; + const course = useModel('courseDetails', courseId); + document.title = getPageHeadTitle(course?.name, intl.formatMessage(messages.headingTitle)); + const { errorFields, savingStatus,