Skip to content

Commit 2af86ef

Browse files
committed
feat: notification count from notification plugin in course outline
1 parent 99de377 commit 2af86ef

6 files changed

Lines changed: 125 additions & 6 deletions

File tree

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { FormattedMessage } from '@edx/frontend-platform/i18n';
2+
import { Icon } from '@openedx/paragon';
3+
import { NotificationsNone } from '@openedx/paragon/icons';
4+
import React from 'react';
5+
import messages from './messages';
6+
7+
interface HooKType {
8+
notificationAppData: {
9+
tabsCount?: {
10+
count?: number;
11+
}
12+
}
13+
}
14+
15+
// Load the hook module asynchronously
16+
function useDynamicHookShim() {
17+
const [hook, setHook] = React.useState<(() => HooKType) | null>(null);
18+
19+
React.useEffect(() => {
20+
let cancelled = false;
21+
22+
async function load() {
23+
try {
24+
const module = await import("@edx/frontend-plugin-notifications");
25+
const hookFn = module.useAppNotifications ?? module.default;
26+
if (!cancelled) {
27+
// `module.useAppNotifications` is itself a hook
28+
setHook(() => hookFn);
29+
}
30+
} catch (err: any) {
31+
// If the module cannot be found, just keep `hook` as null.
32+
console.error("Failed to load notifications plugin:", err);
33+
// No hook – the UI will fall back to the placeholder.
34+
}
35+
}
36+
37+
load();
38+
39+
return () => {
40+
cancelled = true;
41+
};
42+
}, []);
43+
44+
return hook;
45+
}
46+
47+
// Component that actually calls the loaded hook
48+
function NotificationHookConsumer({ hook }: { hook: () => HooKType }) {
49+
// The hook is now called on **every** render of this component
50+
const { notificationAppData } = hook();
51+
52+
if (!notificationAppData?.tabsCount?.count || notificationAppData?.tabsCount?.count < 1) {
53+
return null;
54+
}
55+
56+
// You can use `hookResult` here as needed
57+
return (
58+
<small className="d-flex">
59+
<Icon className="mr-1" size="md" src={NotificationsNone} />
60+
<FormattedMessage
61+
{...messages.notificationMetadataTitle}
62+
values={{ count: notificationAppData?.tabsCount?.count }}
63+
/>
64+
</small>
65+
);
66+
}
67+
68+
// Main component
69+
export const NotificationStatusIcon = () => {
70+
const loadedHook = useDynamicHookShim();
71+
72+
if (!loadedHook) {
73+
return null;
74+
}
75+
76+
// Once loaded, delegate to a component that calls the hook
77+
return <NotificationHookConsumer hook={loadedHook} />;
78+
};
79+

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { CourseOutlineStatusBar } from '@src/course-outline/data/types';
88
import { ChecklistRtl, NotificationsNone } from '@openedx/paragon/icons';
99
import messages from './messages';
1010
import { useWaffleFlags } from '../../data/apiHooks';
11+
import { NotificationStatusIcon } from './NotificationStatusIcon';
1112

1213
const CourseBadge = ({ startDate, endDate }: { startDate: Moment, endDate: Moment }) => {
1314
const now = moment().utc();
@@ -131,12 +132,7 @@ export const StatusBar = ({
131132
startDateRaw={courseReleaseDate}
132133
datesLink={waffleFlags.useNewScheduleDetailsPage ? `/course/${courseId}/settings/details/#schedule` : scheduleDestination()}
133134
/>
134-
{(notificationCount || 0) > 0 && (
135-
<small className="d-flex">
136-
<Icon className="mr-2" size="md" src={NotificationsNone} />
137-
{intl.formatMessage(messages.notificationMetadataTitle, { count: notificationCount })}
138-
</small>
139-
)}
135+
<NotificationStatusIcon />
140136
<Link
141137
className="small text-primary-500 d-flex"
142138
to={`/course/${courseId}/checklists`}

src/data/api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export async function getCourseDetails(courseId: string, username: string): Prom
6767
*/
6868
export const waffleFlagDefaults = {
6969
enableCourseOptimizer: false,
70+
enableNotifications: false,
7071
enableCourseOptimizerCheckPrevRunLinks: false,
7172
useNewHomePage: true,
7273
useNewCustomPages: true,
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export const useAppNotifications = () => {
2+
return {
3+
notificationAppData: {
4+
tabsCount: {
5+
count: 0,
6+
}
7+
}
8+
};
9+
};
10+
11+
export const NotificationsTray = () => <></>;

webpack.dev.config.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const path = require('path');
22
const { createConfig } = require('@openedx/frontend-build');
3+
const webpack = require('webpack');
34

45
const config = createConfig('webpack-dev', {
56
resolve: {
@@ -14,6 +15,21 @@ const config = createConfig('webpack-dev', {
1415
constants: false,
1516
},
1617
},
18+
// Silently ignore “module not found” errors for that exact specifier.
19+
plugins: [
20+
new webpack.NormalModuleReplacementPlugin(
21+
/@edx\/frontend-plugin-notifications/,
22+
(resource) => {
23+
try {
24+
// Try to resolve the real package. If it exists, do nothing.
25+
require.resolve('@edx/frontend-plugin-notifications');
26+
} catch (e) {
27+
// Package not found → point to the stub we created.
28+
resource.request = path.resolve(__dirname, 'src/stubs/empty-notifications-plugin.tsx');
29+
}
30+
}
31+
),
32+
],
1733
});
1834

1935
module.exports = config;

webpack.prod.config.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const path = require('path');
22
const { createConfig } = require('@openedx/frontend-build');
3+
const webpack = require('webpack');
34

45
const config = createConfig('webpack-prod', {
56
resolve: {
@@ -14,6 +15,21 @@ const config = createConfig('webpack-prod', {
1415
constants: false,
1516
},
1617
},
18+
// Silently ignore “module not found” errors for that exact specifier.
19+
plugins: [
20+
new webpack.NormalModuleReplacementPlugin(
21+
/@edx\/frontend-plugin-notifications/,
22+
(resource) => {
23+
try {
24+
// Try to resolve the real package. If it exists, do nothing.
25+
require.resolve('@edx/frontend-plugin-notifications');
26+
} catch (e) {
27+
// Package not found → point to the stub we created.
28+
resource.request = path.resolve(__dirname, 'src/stubs/empty-notifications-plugin.tsx');
29+
}
30+
}
31+
),
32+
],
1733
});
1834

1935
module.exports = config;

0 commit comments

Comments
 (0)