@mistertemp/libs-front-shared 3.0.4 → 4.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/package.json +10 -1
  2. package/src/components/Notifications/CustomerNotificationCenterClient.ts +195 -0
  3. package/src/components/Notifications/EmptyNotificationsList.tsx +33 -0
  4. package/src/components/Notifications/Notification.tsx +105 -0
  5. package/src/components/Notifications/NotificationContent.tsx +153 -0
  6. package/src/components/Notifications/Notifications.module.scss +345 -0
  7. package/src/components/Notifications/NotificationsBell.tsx +242 -0
  8. package/src/components/Notifications/NotificationsCenterContext.tsx +578 -0
  9. package/src/components/Notifications/NotificationsList.tsx +293 -0
  10. package/src/components/Notifications/PopupNotifications.module.scss +73 -0
  11. package/src/components/Notifications/PopupNotifications.tsx +42 -0
  12. package/src/components/Notifications/dateISOToString.ts +32 -0
  13. package/src/components/Notifications/index.ts +7 -0
  14. package/src/components/Notifications/types.ts +39 -0
  15. package/src/components/Notifications/useLiveNotifications.ts +122 -0
  16. package/src/components/index.ts +1 -0
  17. package/src/locales/es/date-picker.json +11 -0
  18. package/src/locales/es/notification-error.json +12 -0
  19. package/src/locales/es/notification-utils.json +7 -0
  20. package/src/locales/es/notification.json +17 -0
  21. package/src/locales/fr/notification-error.json +12 -0
  22. package/src/locales/fr/notification-utils.json +7 -0
  23. package/src/locales/fr/notification.json +17 -0
  24. package/src/locales/it/notification-error.json +12 -0
  25. package/src/locales/it/notification-utils.json +7 -0
  26. package/src/locales/it/notification.json +17 -0
@@ -0,0 +1,345 @@
1
+ .notificationsBell {
2
+ width: 3.2rem;
3
+ height: 3.2rem;
4
+ }
5
+
6
+ .notificationsBellButton {
7
+ color: var(--gray-700);
8
+ }
9
+
10
+ .notificationsBellHasUnreadDot {
11
+ position: relative;
12
+ top: -3.2rem;
13
+ right: -1.6rem;
14
+ color: var(--error-500);
15
+ }
16
+
17
+ .notificationsListLoader {
18
+ display: flex;
19
+ min-height: 20rem;
20
+ flex-direction: column;
21
+ align-items: center;
22
+ justify-content: center;
23
+ margin: auto;
24
+
25
+ > svg {
26
+ width: 5rem;
27
+ height: 5rem;
28
+ }
29
+ }
30
+
31
+ .notificationsListBody {
32
+ display: flex;
33
+ overflow: hidden;
34
+ width: 100%;
35
+ height: 100%;
36
+ flex-flow: row wrap;
37
+ align-items: end;
38
+ gap: 1rem;
39
+
40
+ /* width */
41
+ ::-webkit-scrollbar {
42
+ width: 0.8rem;
43
+ }
44
+
45
+ /* Track */
46
+ ::-webkit-scrollbar-track {
47
+ background: transparent;
48
+ }
49
+
50
+ /* Handle */
51
+ ::-webkit-scrollbar-thumb {
52
+ border-radius: 0.8rem;
53
+ background: var(--gray-200);
54
+ }
55
+
56
+ /* Handle on hover */
57
+ ::-webkit-scrollbar-thumb:hover {
58
+ background: var(--gray-300);
59
+ }
60
+ }
61
+
62
+ .notificationsListUnreadBadge {
63
+ @include text-caption-1;
64
+
65
+ margin-top: 0.2rem;
66
+ margin-bottom: 1rem;
67
+ margin-left: 0.4rem;
68
+ }
69
+
70
+ .emptyNotificationsList {
71
+ display: flex;
72
+ flex-direction: column;
73
+ align-items: center;
74
+ justify-content: center;
75
+ }
76
+
77
+ .emptyNotificationsListIcon {
78
+ margin-top: 0.2rem;
79
+ }
80
+
81
+ .emptyNotificationsListTitle {
82
+ @include text-body-1-bold;
83
+
84
+ margin-top: 1.6rem;
85
+ margin-bottom: 0.4rem;
86
+ text-align: center;
87
+ }
88
+
89
+ .emptyNotificationsListContent {
90
+ @include text-body-2;
91
+
92
+ width: 29.5rem;
93
+ margin-bottom: 0.8rem;
94
+ text-align: center;
95
+ }
96
+
97
+ .notification {
98
+ display: grid;
99
+ padding-top: 1.6rem;
100
+ padding-bottom: 1.6rem;
101
+ margin-right: 0.8rem;
102
+ grid-template-columns: 2.4rem auto;
103
+ transition: padding 0.35s ease-in-out;
104
+
105
+ > :first-child {
106
+ margin-top: 0.2rem;
107
+ margin-right: 0.8rem;
108
+ margin-left: 0.8rem;
109
+ }
110
+
111
+ > a,
112
+ > div {
113
+ all: unset;
114
+ display: flex;
115
+ overflow: hidden;
116
+ max-height: 38.4rem;
117
+ flex-direction: column;
118
+ overflow-wrap: break-word;
119
+ }
120
+
121
+ > svg:hover,
122
+ a:hover {
123
+ cursor: pointer;
124
+ }
125
+ }
126
+
127
+ .notificationLink {
128
+ all: unset;
129
+ display: flex;
130
+ overflow: hidden;
131
+ max-height: 38.4rem;
132
+ flex-direction: column;
133
+ overflow-wrap: break-word;
134
+
135
+ &:hover {
136
+ background-color: var(--gray-50);
137
+ }
138
+ }
139
+
140
+ .notificationsList {
141
+ width: 100%;
142
+ max-height: 42.8rem;
143
+ margin-top: 0.8rem;
144
+ overflow-y: auto;
145
+
146
+ > div {
147
+ > div:first-child {
148
+ padding-top: 0.8rem;
149
+ }
150
+
151
+ > div:not(:last-child) {
152
+ border-bottom: 0.1rem solid var(--gray-200);
153
+ }
154
+ }
155
+ }
156
+
157
+ .notiticationsListFilters {
158
+ display: flex;
159
+ gap: 1rem;
160
+ }
161
+
162
+ .notificationsListFullPageMode {
163
+ overflow: scroll;
164
+ width: 100%;
165
+ height: 100%;
166
+
167
+ > div {
168
+ > div:first-child {
169
+ padding-top: 1.6rem;
170
+ }
171
+
172
+ > div:not(:last-child) {
173
+ border-bottom: 0.1rem solid var(--gray-200);
174
+ }
175
+ }
176
+ }
177
+
178
+ .notificationsListFooter {
179
+ padding-top: 0.4rem;
180
+ padding-bottom: 0.4rem;
181
+ margin: auto;
182
+
183
+ > button {
184
+ @include text-sm-bold;
185
+ }
186
+
187
+ > button:disabled {
188
+ color: var(--gray-300);
189
+ cursor: default;
190
+ }
191
+ }
192
+
193
+ .notificationExpanded {
194
+ padding-bottom: 0;
195
+ }
196
+
197
+ .notificationUnreadDot {
198
+ color: var(--primary-600);
199
+ }
200
+
201
+ .notificationsBellDotCounter {
202
+ position: relative;
203
+ top: -3.7rem;
204
+ left: 0.7rem;
205
+ color: white;
206
+ font-size: 0.9rem;
207
+ }
208
+
209
+ .notificationsBellDotCounterPlus {
210
+ left: 0.5rem;
211
+ }
212
+
213
+ .notificationTitle {
214
+ display: flex;
215
+ flex: 0 0 auto;
216
+ justify-content: space-between;
217
+ padding-right: 1.6rem;
218
+ color: var(--gray-900);
219
+
220
+ @include text-sm-bold;
221
+ }
222
+
223
+ .notificationDate {
224
+ margin-left: 0.4rem;
225
+
226
+ @include text-caption-1;
227
+
228
+ color: var(--gray-400);
229
+ }
230
+
231
+ .notificationClose {
232
+ cursor: pointer;
233
+ }
234
+
235
+ .notificationContent {
236
+ overflow: auto;
237
+ flex: 1 1 auto;
238
+ padding-right: 1.6rem;
239
+ color: var(--gray-600);
240
+
241
+ @include text-body-2;
242
+
243
+ .notificationMessage {
244
+ b {
245
+ @include text-sm-bold;
246
+
247
+ color: var(--gray-600);
248
+ }
249
+ }
250
+
251
+ .notificationAggregatedContent {
252
+ display: grid;
253
+ grid-template-rows: 0fr;
254
+ transition:
255
+ grid-template-rows 0.35s ease-in-out,
256
+ margin 0.35s ease-in-out;
257
+
258
+ > span {
259
+ overflow: hidden;
260
+
261
+ > a {
262
+ display: block;
263
+ margin-top: 0.7rem;
264
+ color: var(--gray-600);
265
+ line-height: inherit;
266
+ text-decoration: none;
267
+
268
+ > b {
269
+ @include text-sm-bold;
270
+
271
+ color: var(--gray-600);
272
+ }
273
+ }
274
+
275
+ > a:last-child {
276
+ margin-bottom: 0.7rem;
277
+ }
278
+
279
+ > a:hover {
280
+ color: var(--gray-700);
281
+
282
+ > b {
283
+ text-decoration: underline;
284
+ }
285
+ }
286
+ }
287
+ }
288
+ }
289
+
290
+ .notificationContentExpanded {
291
+ .notificationAggregatedContent {
292
+ margin-top: 0.7rem;
293
+ grid-template-rows: 1fr;
294
+ }
295
+ }
296
+
297
+ .notificationSeeMore {
298
+ @include text-sm-bold;
299
+
300
+ flex: 0 0 auto;
301
+ color: var(--primary-600);
302
+ cursor: pointer;
303
+ transition: padding 0.35s ease-in-out;
304
+ }
305
+
306
+ .notificationSeeMoreExpanded {
307
+ padding: 0.4rem 0;
308
+ }
309
+
310
+ .notificationsBtnMarkAllRead {
311
+ height: 3.4rem;
312
+ }
313
+
314
+ .notificationsDropDownDate {
315
+ > div:nth-child(2) > div {
316
+ min-width: 31rem;
317
+ max-height: 45rem;
318
+ margin-top: 0.4rem;
319
+ }
320
+
321
+ > div:first-child > div {
322
+ height: 3.2rem;
323
+ }
324
+ }
325
+
326
+ .notificationListRoundedContainer {
327
+ padding: 0.8rem 1.6rem;
328
+ border: 0.1rem solid var(--gray-200);
329
+ border-radius: 1rem;
330
+
331
+ > div.notification {
332
+ margin-right: 0;
333
+ }
334
+ }
335
+
336
+ .emptyNotificationsListTab {
337
+ display: flex;
338
+ flex-direction: column;
339
+ align-items: center;
340
+ justify-content: center;
341
+ }
342
+
343
+ .tabs {
344
+ height: 3.2rem;
345
+ }
@@ -0,0 +1,242 @@
1
+ import {
2
+ Button,
3
+ CloseSvg,
4
+ DotSvg,
5
+ NotificationBellSvg,
6
+ OpenInNewSvg,
7
+ Popover,
8
+ } from '@mistertemp/design-system';
9
+ import { IOTContext, IOTProvider } from '@mistertemp/front-core';
10
+ import classnames from 'classnames';
11
+ import React, { FC, useContext, useMemo, VFC } from 'react';
12
+
13
+ import { useBundledTranslation } from '../../hooks';
14
+ import es from '../../locales/es/notification.json';
15
+ import fr from '../../locales/fr/notification.json';
16
+ import it from '../../locales/it/notification.json';
17
+ import {
18
+ NotificationsCenterContext,
19
+ NotificationsCenterProvider,
20
+ } from './NotificationsCenterContext';
21
+ import styles from './Notifications.module.scss';
22
+ import NotificationsList from './NotificationsList';
23
+ import PopupNotifications from './PopupNotifications';
24
+ import { NotificationCenterClientInterface } from './types';
25
+ import { useLiveNotifications } from './useLiveNotifications';
26
+
27
+ const translationNs = 'notification';
28
+ const translations = { it, fr, es };
29
+
30
+ export type NotificationBellProps = {
31
+ className?: string;
32
+ /**
33
+ * The agency/account ID to scope notifications.
34
+ */
35
+ agencyId: string | null | undefined;
36
+ /**
37
+ * The notification center client instance.
38
+ * Pass `new NotificationCenterClient()` for match (agency),
39
+ * or `new CustomerNotificationCenterClient(env)` for customer.
40
+ */
41
+ client: NotificationCenterClientInterface;
42
+ /**
43
+ * The path to the full-page notifications page.
44
+ * Used for the "open in new window" button and to prevent showing popups on that page.
45
+ * Example: `/a/123/notifications` for match, `/c/notifications` for customer
46
+ */
47
+ notificationsPagePath?: string;
48
+ /**
49
+ * Name of the BroadcastChannel for cross-tab sync.
50
+ * @default 'shared_notifs'
51
+ */
52
+ broadcastChannelName?: string;
53
+ /**
54
+ * Whether to show the "open in new window" button in the popover header.
55
+ * @default false
56
+ */
57
+ showOpenInNewButton?: boolean;
58
+ /**
59
+ * Environment for IOT provider ('dev' | 'sta' | 'prod').
60
+ */
61
+ env?: 'dev' | 'sta' | 'prod';
62
+ /**
63
+ * Locale string for date formatting (e.g. 'fr-FR', 'it-IT').
64
+ */
65
+ locale?: string;
66
+ };
67
+
68
+ type NotificationBellInternalProps = {
69
+ className?: string;
70
+ notificationsPagePath?: string;
71
+ showOpenInNewButton?: boolean;
72
+ locale?: string;
73
+ };
74
+
75
+ const NotificationsBellInternal: VFC<NotificationBellInternalProps> = ({
76
+ className,
77
+ notificationsPagePath,
78
+ showOpenInNewButton = false,
79
+ locale,
80
+ }) => {
81
+ const headerActions = useMemo(() => {
82
+ const actions = [
83
+ {
84
+ name: 'close',
85
+ icon: CloseSvg,
86
+ },
87
+ ];
88
+
89
+ if (showOpenInNewButton && notificationsPagePath) {
90
+ actions.unshift({
91
+ name: 'open_in_new',
92
+ icon: OpenInNewSvg,
93
+ });
94
+ }
95
+
96
+ return actions;
97
+ }, [showOpenInNewButton, notificationsPagePath]);
98
+
99
+ const { t } = useBundledTranslation(translationNs, translations);
100
+ const {
101
+ unseenNotifications,
102
+ unreadNotifications,
103
+ markAllAsRead,
104
+ notifications,
105
+ isPopoverOpen,
106
+ setIsPopoverOpen,
107
+ } = useContext(NotificationsCenterContext);
108
+
109
+ useLiveNotifications({ notificationsPagePath });
110
+
111
+ const handleClosePopover = () => {
112
+ setIsPopoverOpen(false);
113
+ };
114
+
115
+ const handleAllMarkAsRead = () => {
116
+ markAllAsRead();
117
+ };
118
+
119
+ const handlePopOverAction = (name: string) => {
120
+ if (name === 'close') {
121
+ handleClosePopover();
122
+ } else if (name === 'open_in_new' && notificationsPagePath) {
123
+ window.open(notificationsPagePath, '_blank');
124
+ }
125
+ };
126
+
127
+ return (
128
+ <Popover
129
+ data-testid="notificationsPopover"
130
+ size="medium"
131
+ isOpen={isPopoverOpen}
132
+ supportingText=""
133
+ footer={
134
+ notifications.length > 0 && (
135
+ <div className={styles.notificationsListFooter}>
136
+ <Button
137
+ data-testid="notificationsPopover+markAllAsReadButton"
138
+ color="link"
139
+ size="medium"
140
+ text={t('notification:notificationsPopover.markAllAsRead')}
141
+ onClick={handleAllMarkAsRead}
142
+ disabled={unreadNotifications === 0}
143
+ />
144
+ </div>
145
+ )
146
+ }
147
+ onClose={handleClosePopover}
148
+ canClose={true}
149
+ openMode="click"
150
+ placement="bottom-end"
151
+ setOpen={(isOpen) => setIsPopoverOpen(isOpen)}
152
+ title={t('notification:notificationsPopover.title')}
153
+ trigger={
154
+ <div className={classnames(styles.notificationsBell, className)}>
155
+ <Button
156
+ data-testid="notificationsPopover+notificationsBell"
157
+ className={styles.notificationsBellButton}
158
+ size="small"
159
+ color="tertiary-gray"
160
+ leftIcon={NotificationBellSvg}></Button>
161
+ {unseenNotifications > 0 && (
162
+ <>
163
+ <DotSvg
164
+ data-testid="notificationsPopover+notificationsBellUnreadDot"
165
+ className={styles.notificationsBellHasUnreadDot}
166
+ width={12}
167
+ />
168
+ {unseenNotifications > 9 ? (
169
+ <span
170
+ className={classnames(
171
+ styles.notificationsBellDotCounter,
172
+ styles.notificationsBellDotCounterPlus,
173
+ )}>
174
+ 9+
175
+ </span>
176
+ ) : (
177
+ <span className={styles.notificationsBellDotCounter}>
178
+ {unseenNotifications}
179
+ </span>
180
+ )}
181
+ </>
182
+ )}
183
+ </div>
184
+ }
185
+ headerActions={headerActions}
186
+ onHeaderAction={handlePopOverAction}>
187
+ <NotificationsList locale={locale} />
188
+ </Popover>
189
+ );
190
+ };
191
+
192
+ /**
193
+ * Shared NotificationsBell component that can be used in both match and customer contexts.
194
+ *
195
+ * Required contexts from the consuming application:
196
+ * - OAuthContext (from @mistertemp/front-core)
197
+ * - NotificationsContext (from @mistertemp/design-system)
198
+ */
199
+ export const NotificationsBell: FC<NotificationBellProps> = ({
200
+ className,
201
+ agencyId,
202
+ client,
203
+ notificationsPagePath,
204
+ broadcastChannelName,
205
+ showOpenInNewButton = false,
206
+ env = 'dev',
207
+ locale,
208
+ }) => {
209
+ const { isMounted } = useContext(IOTContext);
210
+
211
+ return (
212
+ <NotificationsCenterProvider
213
+ isFullPageMode={false}
214
+ agencyId={agencyId}
215
+ client={client}
216
+ broadcastChannelName={broadcastChannelName}>
217
+ {isMounted ? (
218
+ <>
219
+ <NotificationsBellInternal
220
+ className={className}
221
+ notificationsPagePath={notificationsPagePath}
222
+ showOpenInNewButton={showOpenInNewButton}
223
+ locale={locale}
224
+ />
225
+ <PopupNotifications />
226
+ </>
227
+ ) : (
228
+ <IOTProvider env={env}>
229
+ <>
230
+ <NotificationsBellInternal
231
+ className={className}
232
+ notificationsPagePath={notificationsPagePath}
233
+ showOpenInNewButton={showOpenInNewButton}
234
+ locale={locale}
235
+ />
236
+ <PopupNotifications />
237
+ </>
238
+ </IOTProvider>
239
+ )}
240
+ </NotificationsCenterProvider>
241
+ );
242
+ };