Skip to content

Commit edda4e3

Browse files
committed
fixup! feat: new status bar in course outline
1 parent ad0fd19 commit edda4e3

File tree

5 files changed

+230
-13
lines changed

5 files changed

+230
-13
lines changed

src/course-outline/CourseOutline.tsx

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useState, useEffect, useCallback } from 'react';
22
import { useIntl } from '@edx/frontend-platform/i18n';
3+
import { getConfig } from '@edx/frontend-platform';
34
import {
45
Container,
56
Layout,
@@ -43,7 +44,6 @@ import {
4344
getTimedExamsFlag,
4445
} from './data/selectors';
4546
import { COURSE_BLOCK_NAMES } from './constants';
46-
import StatusBar from './status-bar/StatusBar';
4747
import EnableHighlightsModal from './enable-highlights-modal/EnableHighlightsModal';
4848
import SectionCard from './section-card/SectionCard';
4949
import SubsectionCard from './subsection-card/SubsectionCard';
@@ -62,6 +62,8 @@ import { useCourseOutline } from './hooks';
6262
import messages from './messages';
6363
import { getTagsExportFile } from './data/api';
6464
import OutlineAddChildButtons from './OutlineAddChildButtons';
65+
import { StatusBar } from './status-bar/StatusBar';
66+
import { LegacyStatusBar } from './status-bar/LegacyStatusBar';
6567

6668
interface CourseOutlineProps {
6769
courseId: string,
@@ -143,6 +145,7 @@ const CourseOutline = ({ courseId }: CourseOutlineProps) => {
143145
resetScrollState,
144146
} = useCourseOutline({ courseId });
145147

148+
const showNewActionsBar = getConfig().ENABLE_COURSE_OUTLINE_NEW_DESIGN?.toString().toLowerCase() === 'true';
146149
// Use `setToastMessage` to show the toast.
147150
const [toastMessage, setToastMessage] = useState<string | null>(null);
148151

@@ -343,12 +346,23 @@ const CourseOutline = ({ courseId }: CourseOutlineProps) => {
343346
<article>
344347
<div>
345348
<section className="course-outline-section">
346-
<StatusBar
347-
courseId={courseId}
348-
isLoading={isLoading}
349-
statusBarData={statusBarData}
350-
notificationCount={3}
351-
/>
349+
{showNewActionsBar
350+
? (
351+
<StatusBar
352+
courseId={courseId}
353+
isLoading={isLoading}
354+
statusBarData={statusBarData}
355+
notificationCount={3}
356+
/>
357+
) : (
358+
<LegacyStatusBar
359+
courseId={courseId}
360+
isLoading={isLoading}
361+
statusBarData={statusBarData}
362+
openEnableHighlightsModal={openEnableHighlightsModal}
363+
handleVideoSharingOptionChange={handleVideoSharingOptionChange}
364+
/>
365+
)}
352366
{!errors?.outlineIndexApi && (
353367
<div className="pt-4">
354368
{sections.length ? (

src/course-outline/header-navigations/messages.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,22 @@ const messages = defineMessages({
88
addButton: {
99
id: 'course-authoring.course-outline.header-navigations.button.add-button',
1010
defaultMessage: 'Add',
11+
description: 'Add button text in course outline header',
1112
},
1213
infoButton: {
1314
id: 'course-authoring.course-outline.header-navigations.button.infoButton',
1415
defaultMessage: 'Info',
16+
description: 'Info button text in course outline header',
1517
},
1618
analyticsButton: {
1719
id: 'course-authoring.course-outline.header-navigations.button.analyticsButton',
1820
defaultMessage: 'Analytics',
21+
description: 'Analytics button text in course outline header',
1922
},
2023
helpButton: {
2124
id: 'course-authoring.course-outline.header-navigations.button.helpButton',
2225
defaultMessage: 'Help',
26+
description: 'Help button text in course outline header',
2327
},
2428
newSectionButtonTooltip: {
2529
id: 'course-authoring.course-outline.header-navigations.button.new-section.tooltip',

src/course-outline/messages.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const messages = defineMessages({
88
headingSubtitle: {
99
id: 'course-authoring.course-outline.subTitle',
1010
defaultMessage: 'Course Outline',
11+
description: 'Course Outline heading subTitle.',
1112
},
1213
alertSuccessTitle: {
1314
id: 'course-authoring.course-outline.reindex.alert.success.title',
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
import moment from 'moment/moment';
2+
import { FormattedDate, useIntl } from '@edx/frontend-platform/i18n';
3+
import { getConfig } from '@edx/frontend-platform/config';
4+
import {
5+
Button, Hyperlink, Form, Stack, useToggle,
6+
} from '@openedx/paragon';
7+
import { Link } from 'react-router-dom';
8+
9+
import { ContentTagsDrawerSheet } from '../../content-tags-drawer';
10+
import TagCount from '../../generic/tag-count';
11+
import { useHelpUrls } from '../../help-urls/hooks';
12+
import { useWaffleFlags } from '../../data/apiHooks';
13+
import { VIDEO_SHARING_OPTIONS } from '../constants';
14+
import { useContentTagsCount } from '../../generic/data/apiHooks';
15+
import messages from './messages';
16+
import { getVideoSharingOptionText } from '../utils';
17+
import { ReactNode } from 'react';
18+
import { CourseOutlineStatusBar } from '@src/course-outline/data/types';
19+
20+
interface StatusBarItemProps {
21+
title: string,
22+
children: ReactNode,
23+
};
24+
25+
const StatusBarItem = ({ title, children }: StatusBarItemProps) => (
26+
<div className="d-flex flex-column justify-content-between">
27+
<h5>{title}</h5>
28+
<div className="d-flex align-items-center">
29+
{children}
30+
</div>
31+
</div>
32+
);
33+
34+
interface LegacyStatusBarProps {
35+
courseId: string,
36+
isLoading: boolean,
37+
openEnableHighlightsModal: () => void,
38+
handleVideoSharingOptionChange: (value: string) => void,
39+
statusBarData: CourseOutlineStatusBar,
40+
};
41+
42+
export const LegacyStatusBar = ({
43+
statusBarData,
44+
isLoading,
45+
courseId,
46+
openEnableHighlightsModal,
47+
handleVideoSharingOptionChange,
48+
}: LegacyStatusBarProps) => {
49+
const intl = useIntl();
50+
const waffleFlags = useWaffleFlags(courseId);
51+
52+
const {
53+
courseReleaseDate,
54+
highlightsEnabledForMessaging,
55+
checklist,
56+
isSelfPaced,
57+
videoSharingEnabled,
58+
videoSharingOptions,
59+
} = statusBarData;
60+
61+
const {
62+
completedCourseLaunchChecks,
63+
completedCourseBestPracticesChecks,
64+
totalCourseLaunchChecks,
65+
totalCourseBestPracticesChecks,
66+
} = checklist;
67+
68+
const courseReleaseDateObj = moment.utc(courseReleaseDate, 'MMM DD, YYYY [at] HH:mm UTC', true);
69+
const checkListTitle = `${completedCourseLaunchChecks + completedCourseBestPracticesChecks}/${totalCourseLaunchChecks + totalCourseBestPracticesChecks}`;
70+
const scheduleDestination = () => new URL(`settings/details/${courseId}#schedule`, getConfig().STUDIO_BASE_URL).href;
71+
72+
const {
73+
contentHighlights: contentHighlightsUrl,
74+
socialSharing: socialSharingUrl,
75+
} = useHelpUrls(['contentHighlights', 'socialSharing']);
76+
77+
const { data: courseTagCount } = useContentTagsCount(courseId);
78+
79+
const [isManageTagsDrawerOpen, openManageTagsDrawer, closeManageTagsDrawer] = useToggle(false);
80+
81+
if (isLoading) {
82+
return null;
83+
}
84+
85+
return (
86+
<>
87+
<Stack direction="horizontal" gap={3.5} className="d-flex align-items-stretch outline-status-bar" data-testid="outline-status-bar">
88+
<StatusBarItem title={intl.formatMessage(messages.startDateTitle)}>
89+
<Link
90+
className="small"
91+
to={waffleFlags.useNewScheduleDetailsPage ? `/course/${courseId}/settings/details/#schedule` : scheduleDestination()}
92+
>
93+
{courseReleaseDateObj.isValid() ? (
94+
<FormattedDate
95+
value={courseReleaseDateObj.toString()}
96+
year="numeric"
97+
month="short"
98+
day="2-digit"
99+
hour="numeric"
100+
minute="numeric"
101+
/>
102+
) : courseReleaseDate}
103+
</Link>
104+
</StatusBarItem>
105+
<StatusBarItem title={intl.formatMessage(messages.pacingTypeTitle)}>
106+
<span className="small">
107+
{isSelfPaced
108+
? intl.formatMessage(messages.pacingTypeSelfPaced)
109+
: intl.formatMessage(messages.pacingTypeInstructorPaced)}
110+
</span>
111+
</StatusBarItem>
112+
<StatusBarItem title={intl.formatMessage(messages.checklistTitle)}>
113+
<Link
114+
className="small"
115+
to={`/course/${courseId}/checklists`}
116+
>
117+
{checkListTitle} {intl.formatMessage(messages.checklistCompleted)}
118+
</Link>
119+
</StatusBarItem>
120+
<StatusBarItem title={intl.formatMessage(messages.highlightEmailsTitle)}>
121+
<div className="d-flex align-items-center">
122+
{highlightsEnabledForMessaging ? (
123+
<span data-testid="highlights-enabled-span" className="small">
124+
{intl.formatMessage(messages.highlightEmailsEnabled)}
125+
</span>
126+
) : (
127+
<Button data-testid="highlights-enable-button" size="sm" onClick={openEnableHighlightsModal}>
128+
{intl.formatMessage(messages.highlightEmailsButton)}
129+
</Button>
130+
)}
131+
<Hyperlink
132+
className="small ml-2"
133+
destination={contentHighlightsUrl}
134+
target="_blank"
135+
showLaunchIcon={false}
136+
>
137+
{intl.formatMessage(messages.highlightEmailsLink)}
138+
</Hyperlink>
139+
</div>
140+
</StatusBarItem>
141+
{getConfig().ENABLE_TAGGING_TAXONOMY_PAGES === 'true' && (
142+
<StatusBarItem title={intl.formatMessage(messages.courseTagsTitle)}>
143+
<div className="d-flex align-items-center">
144+
<TagCount count={courseTagCount || 0} />
145+
{ /* eslint-disable-next-line jsx-a11y/anchor-is-valid */ }
146+
<a
147+
className="small ml-2"
148+
href="#"
149+
onClick={openManageTagsDrawer}
150+
>
151+
{intl.formatMessage(messages.courseManageTagsLink)}
152+
</a>
153+
</div>
154+
</StatusBarItem>
155+
)}
156+
{videoSharingEnabled && (
157+
<Form.Group
158+
size="sm"
159+
className="d-flex flex-column justify-content-between m-0"
160+
>
161+
<Form.Label
162+
className="h5"
163+
>{intl.formatMessage(messages.videoSharingTitle)}
164+
</Form.Label>
165+
<div className="d-flex align-items-center">
166+
<Form.Control
167+
as="select"
168+
defaultValue={videoSharingOptions}
169+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleVideoSharingOptionChange(e.target.value)}
170+
>
171+
{Object.values(VIDEO_SHARING_OPTIONS).map((option) => (
172+
<option
173+
key={option}
174+
value={option}
175+
>
176+
{getVideoSharingOptionText(option, messages, intl)}
177+
</option>
178+
))}
179+
</Form.Control>
180+
<Hyperlink
181+
className="small"
182+
destination={socialSharingUrl}
183+
target="_blank"
184+
showLaunchIcon={false}
185+
>
186+
{intl.formatMessage(messages.videoSharingLink)}
187+
</Hyperlink>
188+
</div>
189+
</Form.Group>
190+
191+
)}
192+
</Stack>
193+
<ContentTagsDrawerSheet
194+
id={courseId}
195+
onClose={/* istanbul ignore next */ () => closeManageTagsDrawer()}
196+
showSheet={isManageTagsDrawerOpen}
197+
/>
198+
</>
199+
);
200+
};

src/course-outline/status-bar/StatusBar.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,11 @@ const CourseDatesAndStatus = ({ startDate, endDate, startDateRaw, datesLink }: {
3333
return (
3434
<Stack direction='horizontal' gap={3}>
3535
{courseStatus.active
36-
? <Badge className="px-3 py-1" variant="success">Active</Badge>
36+
? <Badge className="px-3 py-2" variant="success">Active</Badge>
3737
: courseStatus.upcoming
38-
? <Badge className="px-3 py-1 bg-white text-success-400 border border-success-500" variant="success">Upcoming</Badge>
38+
? <Badge className="px-3 py-2 bg-white text-success-400 border border-success-500" variant="success">Upcoming</Badge>
3939
: courseStatus.archived &&
40-
<Badge className="px-3 py-1" variant="light">Archived</Badge>
40+
<Badge className="px-3 py-2" variant="light">Archived</Badge>
4141
}
4242
<Link
4343
className="small text-gray-700"
@@ -72,7 +72,7 @@ interface StatusBarProps {
7272
notificationCount?: number;
7373
};
7474

75-
const StatusBar = ({
75+
export const StatusBar = ({
7676
statusBarData,
7777
isLoading,
7878
courseId,
@@ -125,5 +125,3 @@ const StatusBar = ({
125125
</Stack>
126126
);
127127
};
128-
129-
export default StatusBar;

0 commit comments

Comments
 (0)