Skip to content

Commit ad0fd19

Browse files
committed
feat: new status bar in course outline
1 parent bcea1e2 commit ad0fd19

File tree

3 files changed

+88
-87
lines changed

3 files changed

+88
-87
lines changed

src/course-outline/CourseOutline.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ const CourseOutline = ({ courseId }: CourseOutlineProps) => {
318318
<SubHeader
319319
title={courseName}
320320
subtitle={intl.formatMessage(messages.headingSubtitle)}
321+
hideBorder
321322
headerActions={(
322323
<CourseOutlineHeaderActionsSlot
323324
isReIndexShow={isReIndexShow}
@@ -346,8 +347,7 @@ const CourseOutline = ({ courseId }: CourseOutlineProps) => {
346347
courseId={courseId}
347348
isLoading={isLoading}
348349
statusBarData={statusBarData}
349-
openEnableHighlightsModal={openEnableHighlightsModal}
350-
handleVideoSharingOptionChange={handleVideoSharingOptionChange}
350+
notificationCount={3}
351351
/>
352352
{!errors?.outlineIndexApi && (
353353
<div className="pt-4">

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

Lines changed: 81 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,90 @@
1-
import moment from 'moment/moment';
1+
import moment, { Moment } from 'moment/moment';
22
import { FormattedDate, useIntl } from '@edx/frontend-platform/i18n';
33
import { getConfig } from '@edx/frontend-platform/config';
4-
import {
5-
Button, Hyperlink, Stack,
6-
} from '@openedx/paragon';
4+
import { Badge, Icon, Stack } from '@openedx/paragon';
75
import { Link } from 'react-router-dom';
86

9-
import { useHelpUrls } from '../../help-urls/hooks';
107
import { useWaffleFlags } from '../../data/apiHooks';
118
import messages from './messages';
12-
import { ReactNode } from 'react';
9+
import { useMemo } from 'react';
1310
import { CourseOutlineStatusBar } from '@src/course-outline/data/types';
11+
import { ChecklistRtl, NotificationsNone } from '@openedx/paragon/icons';
1412

15-
interface StatusBarItemProps {
16-
title: string,
17-
children: ReactNode,
18-
};
13+
const CourseDatesAndStatus = ({ startDate, endDate, startDateRaw, datesLink }: {
14+
startDate: Moment;
15+
endDate: Moment;
16+
startDateRaw: string;
17+
datesLink: string;
18+
}) => {
19+
if (!startDate.isValid()) {
20+
// Returns string contained in startDate, i.e. `Set Date`
21+
return <>{startDateRaw}</>;
22+
}
1923

20-
const StatusBarItem = ({ title, children }: StatusBarItemProps) => (
21-
<div className="d-flex flex-column justify-content-between">
22-
<h5>{title}</h5>
23-
<div className="d-flex align-items-center">
24-
{children}
25-
</div>
26-
</div>
27-
);
24+
const courseStatus = useMemo(() => {
25+
const now = moment().utc();
26+
return {
27+
active: now.isBetween(startDate, endDate.isValid() ? endDate : undefined, undefined, '[]'),
28+
upcoming: now.isBefore(startDate),
29+
archived: endDate.isValid() && endDate.isBefore(now),
30+
}
31+
}, [startDate, endDate]);
32+
33+
return (
34+
<Stack direction='horizontal' gap={3}>
35+
{courseStatus.active
36+
? <Badge className="px-3 py-1" variant="success">Active</Badge>
37+
: courseStatus.upcoming
38+
? <Badge className="px-3 py-1 bg-white text-success-400 border border-success-500" variant="success">Upcoming</Badge>
39+
: courseStatus.archived &&
40+
<Badge className="px-3 py-1" variant="light">Archived</Badge>
41+
}
42+
<Link
43+
className="small text-gray-700"
44+
to={datesLink}
45+
>
46+
<FormattedDate
47+
value={startDate.toString()}
48+
year="numeric"
49+
month="short"
50+
day="2-digit"
51+
/>
52+
{endDate.isValid() && (
53+
<>
54+
{" - "}
55+
<FormattedDate
56+
value={endDate.toString()}
57+
year="numeric"
58+
month="short"
59+
day="2-digit"
60+
/>
61+
</>
62+
)}
63+
</Link>
64+
</Stack>
65+
)
66+
};
2867

2968
interface StatusBarProps {
30-
courseId: string,
31-
isLoading: boolean,
32-
openEnableHighlightsModal: () => void,
33-
handleVideoSharingOptionChange: () => void,
34-
statusBarData: CourseOutlineStatusBar,
69+
courseId: string;
70+
isLoading: boolean;
71+
statusBarData: CourseOutlineStatusBar;
72+
notificationCount?: number;
3573
};
3674

3775
const StatusBar = ({
3876
statusBarData,
3977
isLoading,
4078
courseId,
41-
openEnableHighlightsModal,
79+
notificationCount,
4280
}: StatusBarProps) => {
4381
const intl = useIntl();
4482
const waffleFlags = useWaffleFlags(courseId);
4583

4684
const {
4785
endDate,
4886
courseReleaseDate,
49-
highlightsEnabledForMessaging,
5087
checklist,
51-
isSelfPaced,
5288
} = statusBarData;
5389

5490
const {
@@ -63,70 +99,30 @@ const StatusBar = ({
6399
const checkListTitle = `${completedCourseLaunchChecks + completedCourseBestPracticesChecks}/${totalCourseLaunchChecks + totalCourseBestPracticesChecks}`;
64100
const scheduleDestination = () => new URL(`settings/details/${courseId}#schedule`, getConfig().STUDIO_BASE_URL).href;
65101

66-
const {
67-
contentHighlights: contentHighlightsUrl,
68-
} = useHelpUrls(['contentHighlights', 'socialSharing']);
69-
70102
if (isLoading) {
71103
return null;
72104
}
73105

74106
return (
75-
<>
76-
<Stack direction="horizontal" gap={3.5} className="d-flex align-items-stretch outline-status-bar" data-testid="outline-status-bar">
77-
<StatusBarItem title={intl.formatMessage(messages.startDateTitle)}>
78-
<Link
79-
className="small"
80-
to={waffleFlags.useNewScheduleDetailsPage ? `/course/${courseId}/settings/details/#schedule` : scheduleDestination()}
81-
>
82-
{courseReleaseDateObj.isValid() ? (
83-
<FormattedDate
84-
value={courseReleaseDateObj.toString()}
85-
year="numeric"
86-
month="short"
87-
day="2-digit"
88-
/>
89-
) : courseReleaseDate}
90-
</Link>
91-
</StatusBarItem>
92-
<StatusBarItem title={intl.formatMessage(messages.pacingTypeTitle)}>
93-
<span className="small">
94-
{isSelfPaced
95-
? intl.formatMessage(messages.pacingTypeSelfPaced)
96-
: intl.formatMessage(messages.pacingTypeInstructorPaced)}
97-
</span>
98-
</StatusBarItem>
99-
<StatusBarItem title={intl.formatMessage(messages.checklistTitle)}>
100-
<Link
101-
className="small"
102-
to={`/course/${courseId}/checklists`}
103-
>
104-
{checkListTitle} {intl.formatMessage(messages.checklistCompleted)}
105-
</Link>
106-
</StatusBarItem>
107-
<StatusBarItem title={intl.formatMessage(messages.highlightEmailsTitle)}>
108-
<div className="d-flex align-items-center">
109-
{highlightsEnabledForMessaging ? (
110-
<span data-testid="highlights-enabled-span" className="small">
111-
{intl.formatMessage(messages.highlightEmailsEnabled)}
112-
</span>
113-
) : (
114-
<Button data-testid="highlights-enable-button" size="sm" onClick={openEnableHighlightsModal}>
115-
{intl.formatMessage(messages.highlightEmailsButton)}
116-
</Button>
117-
)}
118-
<Hyperlink
119-
className="small ml-2"
120-
destination={contentHighlightsUrl}
121-
target="_blank"
122-
showLaunchIcon={false}
123-
>
124-
{intl.formatMessage(messages.highlightEmailsLink)}
125-
</Hyperlink>
126-
</div>
127-
</StatusBarItem>
128-
</Stack>
129-
</>
107+
<Stack direction="horizontal" gap={4}>
108+
<CourseDatesAndStatus
109+
startDate={courseReleaseDateObj}
110+
endDate={endDateObj}
111+
startDateRaw={courseReleaseDate}
112+
datesLink={waffleFlags.useNewScheduleDetailsPage ? `/course/${courseId}/settings/details/#schedule` : scheduleDestination()}
113+
/>
114+
{(notificationCount || 0) > 0 && <small className="d-flex">
115+
<Icon className="mr-2" size="md" src={NotificationsNone} />
116+
{intl.formatMessage(messages.notificationMetadataTitle, { count: notificationCount })}
117+
</small>}
118+
<Link
119+
className="small text-primary-500 d-flex"
120+
to={`/course/${courseId}/checklists`}
121+
>
122+
<Icon src={ChecklistRtl} size="md" className="mr-2" />
123+
{checkListTitle} {intl.formatMessage(messages.checklistCompleted)}
124+
</Link>
125+
</Stack>
130126
);
131127
};
132128

src/course-outline/status-bar/messages.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ const messages = defineMessages({
2525
id: 'course-authoring.course-outline.status-bar.checklists.completed',
2626
defaultMessage: 'completed',
2727
},
28+
notificationMetadataTitle: {
29+
id: 'course-authoring.course-outline.status-bar.notification-metadata',
30+
defaultMessage: '{count, plural, one {{count} notification} other {{count} notifications}}',
31+
description: 'Metadata notifications text in course outline'
32+
},
2833
highlightEmailsTitle: {
2934
id: 'course-authoring.course-outline.status-bar.highlight-emails',
3035
defaultMessage: 'Course highlight emails',

0 commit comments

Comments
 (0)