Skip to content

Commit ed17006

Browse files
authored
feat: consume notifications center remote api (and expose mock version) (#2005)
1 parent 7e97a0d commit ed17006

File tree

9 files changed

+229
-35
lines changed

9 files changed

+229
-35
lines changed

apps/browser-extension-wallet/src/components/NotificationsCenter/NotificationsBellContainer.tsx

Lines changed: 9 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,39 +3,21 @@ import { useHistory } from 'react-router';
33
import { Dropdown } from 'antd';
44

55
import { walletRoutePaths } from '@routes';
6+
import { useNotificationsCenter } from '@hooks/useNotificationsCenter';
7+
import { useNotificationsCenterConfig } from '@hooks/useNotificationsCenterConfig';
68

79
import { NotificationsBell } from './NotificationsBell';
810
import { NotificationsDropDown } from './NotificationsDropDown';
9-
import { useNotificationsCenterConfig } from '@hooks/useNotificationsCenterConfig';
1011

1112
export interface NotificationsCenterContainerProps {
1213
popupView?: boolean;
1314
}
1415

1516
export const NotificationsBellContainer = ({ popupView }: NotificationsCenterContainerProps): React.ReactElement => {
17+
const { isNotificationsCenterEnabled } = useNotificationsCenterConfig();
18+
const { markAsRead, notifications, unreadNotifications } = useNotificationsCenter();
1619
const history = useHistory();
1720
const [isOpen, setIsOpen] = useState(false);
18-
const { isNotificationsCenterEnabled } = useNotificationsCenterConfig();
19-
20-
// TODO Connect with notifications center
21-
const [notifications, setNotifications] = useState<string[]>([]);
22-
23-
const handleOpenChange = (open: boolean) => {
24-
setIsOpen(open);
25-
26-
// TODO remove this placeholder logic
27-
if (!open) {
28-
setNotifications(
29-
// eslint-disable-next-line no-magic-numbers
30-
notifications.length === 11 ? [] : [...notifications, `Notification ${notifications.length + 1}`]
31-
);
32-
}
33-
};
34-
35-
const handleMarkAllAsRead = () => {
36-
// TODO remove this placeholder logic
37-
setNotifications([]);
38-
};
3921

4022
const handleViewAll = () => {
4123
setIsOpen(false);
@@ -45,20 +27,22 @@ export const NotificationsBellContainer = ({ popupView }: NotificationsCenterCon
4527
return (
4628
isNotificationsCenterEnabled && (
4729
<Dropdown
48-
onOpenChange={handleOpenChange}
30+
onOpenChange={setIsOpen}
4931
open={isOpen}
5032
dropdownRender={() => (
5133
<NotificationsDropDown
5234
notifications={notifications}
53-
onMarkAllAsRead={handleMarkAllAsRead}
35+
onMarkAllAsRead={() => markAsRead()}
36+
onMarkAsRead={(id: string) => markAsRead(id)}
5437
onViewAll={handleViewAll}
5538
popupView={popupView}
39+
unreadNotifications={unreadNotifications}
5640
/>
5741
)}
5842
placement="bottomRight"
5943
trigger={['click']}
6044
>
61-
<NotificationsBell onClick={() => setIsOpen(!isOpen)} unreadNotifications={notifications.length} />
45+
<NotificationsBell onClick={() => setIsOpen(!isOpen)} unreadNotifications={unreadNotifications} />
6246
</Dropdown>
6347
)
6448
);

apps/browser-extension-wallet/src/components/NotificationsCenter/NotificationsCenter.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,26 @@ import { Box, Flex } from '@input-output-hk/lace-ui-toolkit';
44

55
import { Button, NavigationButton } from '@lace/common';
66
import { SectionTitle } from '@components/Layout/SectionTitle';
7+
import { LaceNotification, NotificationsTopic } from '@src/types/notifications-center';
78

89
import styles from './NotificationsCenter.module.scss';
910
import { WarningModal } from '@src/views/browser-view/components/WarningModal/WarningModal';
1011

1112
export interface NotificationsCenterProps {
13+
notifications: LaceNotification[];
1214
onBack: () => void;
1315
onMarkAllAsRead: () => void;
16+
onMarkAsRead: (id: string) => void;
1417
popupView?: boolean;
18+
topics: NotificationsTopic[];
19+
unreadNotifications: number;
1520
}
1621

1722
export const NotificationsCenter = ({
1823
onBack,
1924
onMarkAllAsRead,
20-
popupView
25+
popupView,
26+
unreadNotifications
2127
}: NotificationsCenterProps): React.ReactElement => {
2228
const { t } = useTranslation();
2329
const [isRemoveNotificationModalVisible, setIsRemoveNotificationModalVisible] = useState(false);
@@ -45,7 +51,7 @@ export const NotificationsCenter = ({
4551
<Flex justifyContent="space-between" mb={'$44'}>
4652
<Box mb={'$0'}>
4753
<SectionTitle
48-
sideText={`(${1})`}
54+
sideText={unreadNotifications > 0 ? `(${unreadNotifications})` : undefined}
4955
title={
5056
<Flex alignItems="center" gap="$8">
5157
<NavigationButton icon="arrow" onClick={onBack} />

apps/browser-extension-wallet/src/components/NotificationsCenter/NotificationsCenterContainer.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,24 @@ import { useHistory } from 'react-router';
33

44
import { useWalletStore } from '@stores';
55
import { APP_MODE_POPUP } from '@src/utils/constants';
6+
import { useNotificationsCenter } from '@hooks/useNotificationsCenter';
67

78
import { NotificationsCenter } from './NotificationsCenter';
89

910
export const NotificationsCenterContainer = (): React.ReactElement => {
1011
const history = useHistory();
1112
const { walletUI } = useWalletStore();
13+
const { markAsRead, notifications, topics, unreadNotifications } = useNotificationsCenter();
1214

1315
return (
1416
<NotificationsCenter
17+
notifications={notifications}
1518
onBack={() => history.goBack()}
16-
onMarkAllAsRead={() => {
17-
// TODO connect with notifications center
18-
// eslint-disable-next-line no-console
19-
console.log('onMarkAllAsRead');
20-
}}
19+
onMarkAllAsRead={() => markAsRead()}
20+
onMarkAsRead={(id: string) => void markAsRead(id)}
2121
popupView={walletUI.appMode === APP_MODE_POPUP}
22+
topics={topics}
23+
unreadNotifications={unreadNotifications}
2224
/>
2325
);
2426
};

apps/browser-extension-wallet/src/components/NotificationsCenter/NotificationsDropDown.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,27 @@ import { Menu } from 'antd';
55

66
import { Divider, Flex, Text } from '@input-output-hk/lace-ui-toolkit';
77

8+
import { LaceNotification } from '@src/types/notifications-center';
9+
810
import { NotificationsAllClear } from './NotificationsAllClear';
911

1012
import styles from './NotificationsDropDown.module.scss';
1113

1214
export interface NotificationsDropDownProps {
13-
notifications: string[];
15+
notifications: LaceNotification[];
1416
onMarkAllAsRead: () => void;
17+
onMarkAsRead: (id: string) => void;
1518
onViewAll: () => void;
1619
popupView?: boolean;
20+
unreadNotifications: number;
1721
}
1822

1923
export const NotificationsDropDown = ({
2024
notifications,
2125
onMarkAllAsRead,
2226
onViewAll,
23-
popupView
27+
popupView,
28+
unreadNotifications
2429
}: NotificationsDropDownProps): React.ReactElement => {
2530
const { t } = useTranslation();
2631

@@ -36,7 +41,7 @@ export const NotificationsDropDown = ({
3641
{t(`notificationsCenter.${notifications.length > 0 ? 'viewAll' : 'manageSubscriptions'}`)}
3742
</a>
3843
</Text.Body.Normal>
39-
{notifications.length > 0 && (
44+
{unreadNotifications > 0 && (
4045
<Text.Body.Normal>
4146
<a onClick={onMarkAllAsRead}>{t('notificationsCenter.markAllAsRead')}</a>
4247
</Text.Body.Normal>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { useMemo } from 'react';
2+
import { runtime } from 'webextension-polyfill';
3+
4+
import { consumeRemoteApi } from '@cardano-sdk/web-extension';
5+
import { logger, useObservable } from '@lace/common';
6+
import {
7+
LaceNotification,
8+
notificationsCenterProperties,
9+
NotificationsCenterProperties,
10+
NotificationsTopic
11+
} from '@src/types/notifications-center';
12+
13+
const notificationsCenterApi = consumeRemoteApi<NotificationsCenterProperties>(
14+
{
15+
baseChannel: 'notifications-center',
16+
properties: notificationsCenterProperties
17+
},
18+
{ logger, runtime }
19+
);
20+
21+
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
22+
export const useNotificationsCenter = () => {
23+
const { markAsRead, notifications$, remove } = notificationsCenterApi.notifications;
24+
const { topics$, subscribe, unsubscribe } = notificationsCenterApi.topics;
25+
26+
const notifications = useObservable<LaceNotification[]>(notifications$);
27+
const topics = useObservable<NotificationsTopic[]>(topics$);
28+
29+
const unreadNotifications = useMemo(
30+
() => notifications?.reduce((unreadCounter, { read }) => unreadCounter + (read ? 0 : 1), 0) ?? 0,
31+
[notifications]
32+
);
33+
34+
return {
35+
notifications,
36+
unreadNotifications,
37+
topics,
38+
subscribe,
39+
unsubscribe,
40+
markAsRead,
41+
remove
42+
};
43+
};

apps/browser-extension-wallet/src/lib/scripts/background/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ import './keep-alive-sw';
99
import './onUninstall';
1010
import './nami-migration';
1111
import './onStorageChange';
12+
import './notifications-center';
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { runtime } from 'webextension-polyfill';
2+
import { of, Subject } from 'rxjs';
3+
import { exposeApi } from '@cardano-sdk/web-extension';
4+
5+
import {
6+
LaceNotification,
7+
NotificationsCenterProperties,
8+
notificationsCenterProperties,
9+
NotificationsTopic
10+
} from '@src/types/notifications-center';
11+
import { logger } from '@lace/common';
12+
13+
const exposeNotificationsCenterAPI = (): void => {
14+
let notifications: LaceNotification[] = [
15+
{
16+
message: {
17+
body: 'The Glacier Drop phase 2 is live',
18+
chain: 'Midnight',
19+
format: 'plain',
20+
id: 'id-1',
21+
publisher: 'Midnight',
22+
topic: 'topic-1',
23+
title: 'The Glacier Drop phase 2 is live'
24+
}
25+
},
26+
{
27+
message: {
28+
body: 'The new node version XYZ is out',
29+
chain: 'Midnight',
30+
format: 'plain',
31+
id: 'id-2',
32+
publisher: 'Midnight',
33+
topic: 'topic-2',
34+
title: 'The new node version XYZ is out'
35+
}
36+
},
37+
{
38+
message: {
39+
body: 'The governance council has opened voting for governance action number 26.\nNIGHT holders are welcome to cast their votes until Aug-31 via the portal at\n\nhttps://governance.midnight.network',
40+
chain: 'Cardano',
41+
format: 'plain',
42+
id: 'id-3',
43+
publisher: 'Governance',
44+
topic: 'topic-1',
45+
title: 'The governance council has opened voting for governance action number 26'
46+
},
47+
read: true
48+
}
49+
];
50+
51+
const topics: NotificationsTopic[] = [{ name: 'topic-1' }, { name: 'topic-2', subscribed: true }];
52+
53+
const notifications$ = new Subject<LaceNotification[]>();
54+
const topics$ = new Subject<NotificationsTopic[]>();
55+
56+
const markAsRead = async (id?: string): Promise<void> => {
57+
for (const notification of notifications) if (notification.message.id === id || !id) notification.read = true;
58+
59+
notifications$.next(notifications);
60+
61+
return Promise.resolve();
62+
};
63+
64+
const remove = async (id: string): Promise<void> => {
65+
notifications = notifications.filter((notification) => notification.message.id !== id);
66+
67+
notifications$.next(notifications);
68+
69+
return Promise.resolve();
70+
};
71+
72+
const subscribe = async (topic: string): Promise<void> => {
73+
for (const currTopic of topics) if (currTopic.name === topic) currTopic.subscribed = true;
74+
75+
topics$.next(topics);
76+
77+
return Promise.resolve();
78+
};
79+
80+
const unsubscribe = async (topic: string): Promise<void> => {
81+
for (const currTopic of topics) if (currTopic.name === topic) delete currTopic.subscribed;
82+
83+
topics$.next(topics);
84+
85+
return Promise.resolve();
86+
};
87+
88+
exposeApi<NotificationsCenterProperties>(
89+
{
90+
api$: of({
91+
notifications: { markAsRead, notifications$, remove },
92+
topics: { topics$, subscribe, unsubscribe }
93+
}),
94+
baseChannel: 'notifications-center',
95+
properties: notificationsCenterProperties
96+
},
97+
{ logger, runtime }
98+
);
99+
100+
notifications$.next(notifications);
101+
topics$.next(topics);
102+
};
103+
104+
if (!(globalThis as unknown as { LMP_BUNDLE: boolean }).LMP_BUNDLE) exposeNotificationsCenterAPI();

apps/browser-extension-wallet/src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ export * from './ui';
1010
export * from './side-menu';
1111
export * from './pgp';
1212
export * from './paperWallet';
13+
export * from './notifications-center';
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { Observable } from 'rxjs';
2+
import { RemoteApiProperties, RemoteApiPropertyType } from '@cardano-sdk/web-extension';
3+
4+
export interface LaceMessage {
5+
body: string;
6+
chain: string;
7+
format: string;
8+
id: string;
9+
publisher: string;
10+
title: string;
11+
topic: string;
12+
}
13+
14+
export interface LaceNotification {
15+
message: LaceMessage;
16+
read?: boolean;
17+
}
18+
19+
export interface NotificationsTopic {
20+
name: string;
21+
subscribed?: boolean;
22+
}
23+
24+
export interface NotificationsCenterProperties {
25+
notifications: {
26+
markAsRead: (id?: string) => Promise<void>; // markAsRead() marks all as read
27+
notifications$: Observable<LaceNotification[]>;
28+
remove: (id: string) => Promise<void>;
29+
};
30+
topics: {
31+
topics$: Observable<NotificationsTopic[]>;
32+
subscribe: (topic: string) => Promise<void>;
33+
unsubscribe: (topic: string) => Promise<void>;
34+
};
35+
}
36+
37+
export const notificationsCenterProperties: RemoteApiProperties<NotificationsCenterProperties> = {
38+
notifications: {
39+
markAsRead: RemoteApiPropertyType.MethodReturningPromise,
40+
notifications$: RemoteApiPropertyType.HotObservable,
41+
remove: RemoteApiPropertyType.MethodReturningPromise
42+
},
43+
topics: {
44+
topics$: RemoteApiPropertyType.HotObservable,
45+
subscribe: RemoteApiPropertyType.MethodReturningPromise,
46+
unsubscribe: RemoteApiPropertyType.MethodReturningPromise
47+
}
48+
};

0 commit comments

Comments
 (0)