@stack-spot/portal-components 2.27.0 → 2.27.2
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.
- package/CHANGELOG.md +635 -621
- package/dist/components/AnimatedHeight.d.ts +1 -1
- package/dist/components/AnimatedHeight.js +26 -26
- package/dist/components/AsyncContent.d.ts +1 -1
- package/dist/components/AsyncContent.js +1 -1
- package/dist/components/BannerWarning.d.ts +1 -1
- package/dist/components/BannerWarning.js +1 -1
- package/dist/components/Breadcrumb/index.d.ts +2 -2
- package/dist/components/Breadcrumb/index.js +1 -1
- package/dist/components/Breadcrumb/styled.js +31 -31
- package/dist/components/ButtonLoading.d.ts +1 -1
- package/dist/components/ButtonLoading.js +1 -1
- package/dist/components/ChatBot.d.ts +1 -1
- package/dist/components/ChatBot.js +1 -1
- package/dist/components/ContentValidateFilter.d.ts +1 -1
- package/dist/components/ContentValidateFilter.js +1 -1
- package/dist/components/FadingOverflow.d.ts +1 -1
- package/dist/components/FadingOverflow.js +69 -69
- package/dist/components/FileTreeView/More.d.ts +1 -1
- package/dist/components/FileTreeView/More.js +1 -1
- package/dist/components/FileTreeView/index.d.ts +1 -1
- package/dist/components/FileTreeView/index.js +1 -1
- package/dist/components/InfiniteScroll.d.ts +1 -1
- package/dist/components/InfiniteScroll.js +1 -1
- package/dist/components/InfoMaintenanceBanner.d.ts +1 -1
- package/dist/components/InfoMaintenanceBanner.js +2 -2
- package/dist/components/LazyMarkdown/BlockquoteMd.d.ts +1 -1
- package/dist/components/LazyMarkdown/BlockquoteMd.js +1 -1
- package/dist/components/LazyMarkdown/CodeViewer.d.ts +1 -1
- package/dist/components/LazyMarkdown/CodeViewer.js +76 -76
- package/dist/components/LazyMarkdown/Markdown.d.ts +1 -1
- package/dist/components/LazyMarkdown/Markdown.js +1 -1
- package/dist/components/LazyMarkdown/MarkdownButton.d.ts +1 -1
- package/dist/components/LazyMarkdown/MarkdownButton.js +1 -1
- package/dist/components/LazyMarkdown/Video.d.ts +1 -1
- package/dist/components/LazyMarkdown/Video.js +1 -1
- package/dist/components/LazyMarkdown/index.d.ts +1 -1
- package/dist/components/LazyMarkdown/index.js +1 -1
- package/dist/components/Placeholder.d.ts +7 -3
- package/dist/components/Placeholder.d.ts.map +1 -1
- package/dist/components/Placeholder.js +3 -3
- package/dist/components/Placeholder.js.map +1 -1
- package/dist/components/ScrollView.js +16 -16
- package/dist/components/Select/BadgeItem.d.ts +1 -1
- package/dist/components/Select/BadgeItem.js +1 -1
- package/dist/components/Select/ClearInput.d.ts +1 -1
- package/dist/components/Select/ClearInput.js +1 -1
- package/dist/components/Select/CloseItem.d.ts +1 -1
- package/dist/components/Select/CloseItem.js +1 -1
- package/dist/components/Select/CreatableSelect.js +1 -1
- package/dist/components/Select/CustomMenu.d.ts +1 -1
- package/dist/components/Select/CustomMenu.js +1 -1
- package/dist/components/Select/LabelItem.d.ts +1 -1
- package/dist/components/Select/LabelItem.js +1 -1
- package/dist/components/Select/MultiValue.d.ts +1 -1
- package/dist/components/Select/MultiValue.js +1 -1
- package/dist/components/Select/SelectInfiniteScroll.d.ts +1 -1
- package/dist/components/Select/SelectInfiniteScroll.js +1 -1
- package/dist/components/Select/SelectSearch.d.ts +1 -1
- package/dist/components/Select/SelectSearch.js +1 -1
- package/dist/components/SelectionList.d.ts +1 -1
- package/dist/components/SelectionList.js +61 -61
- package/dist/components/StatusCircle.d.ts +1 -1
- package/dist/components/StatusCircle.js +6 -6
- package/dist/components/Stepper/Navigation.js +4 -4
- package/dist/components/Stepper/Step.js +3 -3
- package/dist/components/Stepper/Stepper.js +6 -6
- package/dist/components/Stepper/headers.js +22 -22
- package/dist/components/Table/HeaderItem.js +1 -1
- package/dist/components/Table/SettingsVerticalMenu.d.ts +1 -1
- package/dist/components/Table/SettingsVerticalMenu.js +1 -1
- package/dist/components/Table/StyledLinkTable.d.ts +1 -1
- package/dist/components/Table/StyledLinkTable.js +5 -5
- package/dist/components/Table/TableData.d.ts +1 -1
- package/dist/components/Table/TableData.js +25 -25
- package/dist/components/TimelineSection.d.ts +1 -1
- package/dist/components/TimelineSection.js +14 -14
- package/dist/components/error/ErrorFeedback.d.ts +1 -1
- package/dist/components/error/ErrorFeedback.js +35 -35
- package/dist/components/error/NotFound.d.ts +1 -1
- package/dist/components/error/NotFound.js +1 -1
- package/dist/components/error/UnderMaintenance.d.ts +1 -1
- package/dist/components/error/UnderMaintenance.js +1 -1
- package/dist/components/form/Form/Form.d.ts +1 -1
- package/dist/components/form/Form/Form.js +1 -1
- package/dist/components/form/Form/FormGroup.d.ts +2 -2
- package/dist/components/form/Form/FormGroup.js +1 -1
- package/dist/components/form/SearchInput.d.ts +1 -1
- package/dist/components/form/SearchInput.js +1 -1
- package/dist/components/form/Select/CustomSelect.d.ts +1 -1
- package/dist/components/form/Select/CustomSelect.js +1 -1
- package/dist/components/form/Select/DetailedSelect.d.ts +1 -1
- package/dist/components/form/Select/DetailedSelect.js +1 -1
- package/dist/components/form/Select/Select.d.ts +1 -1
- package/dist/components/form/Select/Select.js +1 -1
- package/dist/components/form/Select/styled.js +161 -161
- package/dist/components/form/Select/utils.js +1 -1
- package/dist/components/notification/NotificationComponent.d.ts +1 -1
- package/dist/components/notification/NotificationComponent.js +54 -54
- package/dist/components/notification/NotificationItem.d.ts +1 -1
- package/dist/components/notification/NotificationItem.d.ts.map +1 -1
- package/dist/components/notification/NotificationItem.js +11 -5
- package/dist/components/notification/NotificationItem.js.map +1 -1
- package/dist/components/notification/NotificationList.d.ts +1 -1
- package/dist/components/notification/NotificationList.d.ts.map +1 -1
- package/dist/components/notification/NotificationList.js +44 -44
- package/dist/components/notification/NotificationList.js.map +1 -1
- package/dist/components/notification/NotificationPlaceholder.d.ts +1 -1
- package/dist/components/notification/NotificationPlaceholder.d.ts.map +1 -1
- package/dist/components/notification/NotificationPlaceholder.js +2 -2
- package/dist/components/notification/NotificationPlaceholder.js.map +1 -1
- package/dist/containers/NotificationsPage.d.ts +1 -1
- package/dist/containers/NotificationsPage.d.ts.map +1 -1
- package/dist/containers/NotificationsPage.js +24 -11
- package/dist/containers/NotificationsPage.js.map +1 -1
- package/dist/context/anchor.d.ts +1 -1
- package/dist/context/anchor.js +1 -1
- package/dist/context/loading.d.ts +1 -1
- package/dist/context/loading.js +1 -1
- package/dist/context/notification/context.d.ts +1 -1
- package/dist/context/notification/context.js +1 -1
- package/dist/context/notification/types.d.ts +1 -0
- package/dist/context/notification/types.d.ts.map +1 -1
- package/dist/hooks/date.js +1 -1
- package/dist/hooks/service-now.js +28 -28
- package/dist/svg/AI.d.ts +1 -1
- package/dist/svg/AI.js +1 -1
- package/dist/svg/CS.d.ts +1 -1
- package/dist/svg/CS.js +1 -1
- package/dist/svg/EDP.d.ts +1 -1
- package/dist/svg/EDP.js +1 -1
- package/dist/svg/Forbidden.d.ts +1 -1
- package/dist/svg/Forbidden.js +1 -1
- package/dist/svg/GenericPlaceholder.d.ts +4 -2
- package/dist/svg/GenericPlaceholder.d.ts.map +1 -1
- package/dist/svg/GenericPlaceholder.js +2 -2
- package/dist/svg/GenericPlaceholder.js.map +1 -1
- package/dist/svg/HUB.d.ts +1 -1
- package/dist/svg/HUB.js +1 -1
- package/dist/svg/Logo.d.ts +1 -1
- package/dist/svg/Logo.js +1 -1
- package/dist/svg/MiniLogo.d.ts +1 -1
- package/dist/svg/MiniLogo.js +1 -1
- package/dist/svg/NotFound.d.ts +1 -1
- package/dist/svg/NotFound.js +1 -1
- package/dist/svg/ServerError.d.ts +1 -1
- package/dist/svg/ServerError.js +1 -1
- package/dist/svg/Unauthenticated.d.ts +1 -1
- package/dist/svg/Unauthenticated.js +1 -1
- package/package.json +6 -6
- package/readme.md +66 -66
- package/src/components/AnimatedHeight.tsx +174 -174
- package/src/components/AsyncContent.tsx +78 -78
- package/src/components/BannerWarning.tsx +91 -91
- package/src/components/Breadcrumb/index.tsx +76 -76
- package/src/components/Breadcrumb/styled.ts +37 -37
- package/src/components/ButtonLoading.tsx +29 -29
- package/src/components/ChatBot.tsx +82 -82
- package/src/components/ContentValidateFilter.tsx +15 -15
- package/src/components/FadingOverflow.tsx +265 -265
- package/src/components/FileTreeView/More.tsx +114 -114
- package/src/components/FileTreeView/index.tsx +186 -186
- package/src/components/InfiniteScroll.tsx +24 -24
- package/src/components/InfoMaintenanceBanner.tsx +29 -29
- package/src/components/LazyMarkdown/BlockquoteMd.tsx +107 -107
- package/src/components/LazyMarkdown/CodeViewer.tsx +161 -161
- package/src/components/LazyMarkdown/Markdown.tsx +122 -122
- package/src/components/LazyMarkdown/MarkdownButton.tsx +24 -24
- package/src/components/LazyMarkdown/Video.tsx +13 -13
- package/src/components/LazyMarkdown/index.tsx +21 -21
- package/src/components/Placeholder.tsx +123 -118
- package/src/components/ScrollView.tsx +57 -57
- package/src/components/Select/BadgeItem.tsx +58 -58
- package/src/components/Select/ClearInput.tsx +24 -24
- package/src/components/Select/CloseItem.tsx +38 -38
- package/src/components/Select/CreatableSelect.tsx +155 -155
- package/src/components/Select/CustomMenu.tsx +16 -16
- package/src/components/Select/LabelItem.tsx +8 -8
- package/src/components/Select/MultiValue.tsx +49 -49
- package/src/components/Select/SelectInfiniteScroll.tsx +82 -82
- package/src/components/Select/SelectSearch.tsx +195 -195
- package/src/components/Select/index.tsx +7 -7
- package/src/components/Select/types.ts +8 -8
- package/src/components/SelectionList.tsx +427 -427
- package/src/components/StatusCircle.tsx +67 -67
- package/src/components/Stepper/Navigation.tsx +97 -97
- package/src/components/Stepper/Step.tsx +30 -30
- package/src/components/Stepper/Stepper.tsx +113 -113
- package/src/components/Stepper/headers.tsx +64 -64
- package/src/components/Stepper/index.ts +3 -3
- package/src/components/Table/HeaderItem.tsx +52 -52
- package/src/components/Table/SettingsVerticalMenu.tsx +50 -50
- package/src/components/Table/StyledLinkTable.tsx +22 -22
- package/src/components/Table/TableData.tsx +251 -251
- package/src/components/Table/index.tsx +2 -2
- package/src/components/TimelineSection.tsx +66 -66
- package/src/components/error/ErrorFeedback.tsx +217 -217
- package/src/components/error/NotFound.tsx +24 -24
- package/src/components/error/UnderMaintenance.tsx +30 -30
- package/src/components/error/index.ts +4 -4
- package/src/components/form/Form/Form.tsx +101 -101
- package/src/components/form/Form/FormGroup.tsx +221 -221
- package/src/components/form/Form/index.ts +2 -2
- package/src/components/form/SearchInput.tsx +69 -69
- package/src/components/form/Select/CustomSelect.tsx +232 -232
- package/src/components/form/Select/DetailedSelect.tsx +85 -85
- package/src/components/form/Select/Select.tsx +67 -67
- package/src/components/form/Select/index.ts +4 -4
- package/src/components/form/Select/styled.ts +165 -165
- package/src/components/form/Select/types.ts +112 -112
- package/src/components/form/Select/utils.tsx +28 -28
- package/src/components/notification/NotificationComponent.tsx +340 -340
- package/src/components/notification/NotificationItem.tsx +345 -336
- package/src/components/notification/NotificationList.tsx +179 -178
- package/src/components/notification/NotificationPlaceholder.tsx +44 -43
- package/src/components/notification/types.ts +72 -72
- package/src/containers/NotificationsPage.tsx +119 -98
- package/src/context/anchor.tsx +37 -37
- package/src/context/loading.tsx +36 -36
- package/src/context/notification/LazyNotificationList.ts +103 -103
- package/src/context/notification/NotificationController.ts +104 -104
- package/src/context/notification/context.tsx +23 -23
- package/src/context/notification/hooks.ts +98 -98
- package/src/context/notification/types.ts +66 -65
- package/src/hooks/date.ts +31 -31
- package/src/hooks/keyboard.tsx +128 -128
- package/src/hooks/manual-render.tsx +10 -10
- package/src/hooks/service-now.tsx +233 -233
- package/src/hooks/text.tsx +30 -30
- package/src/hooks/title.tsx +28 -28
- package/src/hooks/use-effect-once.tsx +43 -43
- package/src/index.ts +19 -19
- package/src/notifications.ts +11 -11
- package/src/svg/AI.tsx +41 -41
- package/src/svg/CS.tsx +48 -48
- package/src/svg/EDP.tsx +31 -31
- package/src/svg/Forbidden.tsx +22 -22
- package/src/svg/GenericPlaceholder.tsx +20 -20
- package/src/svg/HUB.tsx +48 -48
- package/src/svg/Logo.tsx +16 -16
- package/src/svg/MiniLogo.tsx +12 -12
- package/src/svg/NotFound.tsx +16 -16
- package/src/svg/ServerError.tsx +33 -33
- package/src/svg/Unauthenticated.tsx +16 -16
- package/src/svg/index.ts +11 -11
- package/src/utils/accessibility.ts +135 -135
- package/src/utils/cookie.ts +73 -73
- package/src/utils/promise.ts +5 -5
- package/src/utils/read-file.ts +16 -16
- package/tsconfig.json +10 -10
|
@@ -1,104 +1,104 @@
|
|
|
1
|
-
import { pull } from 'lodash'
|
|
2
|
-
import { LazyNotificationList } from './LazyNotificationList'
|
|
3
|
-
import { LoadNotificationsFilters, NotificationConfig, UnreadNotificationListener } from './types'
|
|
4
|
-
|
|
5
|
-
const DEFAULT_POLLING_MS = 120000
|
|
6
|
-
|
|
7
|
-
export class NotificationController {
|
|
8
|
-
private lazyLists = new Map<number, LazyNotificationList>()
|
|
9
|
-
readonly config: NotificationConfig
|
|
10
|
-
private readStatusMap = new Map<string, boolean>()
|
|
11
|
-
private nextId = 1
|
|
12
|
-
private unreadNotification = false
|
|
13
|
-
private lastUnreadVerification: Date | undefined
|
|
14
|
-
private pollingTimeoutId: number | undefined
|
|
15
|
-
private unreadNotificationListeners: UnreadNotificationListener[] = []
|
|
16
|
-
|
|
17
|
-
constructor(config: NotificationConfig) {
|
|
18
|
-
this.config = config
|
|
19
|
-
config.pollingMS ??= DEFAULT_POLLING_MS
|
|
20
|
-
config.notificationsPath ??= '/notifications'
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
private hasUnreadNotificationInMemory() {
|
|
24
|
-
for (const [, read] of this.readStatusMap) {
|
|
25
|
-
if (!read.valueOf()) return true
|
|
26
|
-
}
|
|
27
|
-
return false
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
private setUnreadNotification(value: boolean) {
|
|
31
|
-
this.unreadNotification = value
|
|
32
|
-
if (!value) {
|
|
33
|
-
this.pollingTimeoutId = window.setTimeout(() => this.checkUnread(), this.config.pollingMS)
|
|
34
|
-
}
|
|
35
|
-
this.unreadNotificationListeners.forEach(l => l(value))
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
createLazyNotificationList(filters?: LoadNotificationsFilters): LazyNotificationList {
|
|
39
|
-
const list = new LazyNotificationList({
|
|
40
|
-
id: this.nextId++,
|
|
41
|
-
filters,
|
|
42
|
-
load: async (options) => {
|
|
43
|
-
const result = await this.config.load(options)
|
|
44
|
-
result.items.forEach((i) => {
|
|
45
|
-
this.readStatusMap.set(i.id, i.committed)
|
|
46
|
-
// updating the unread indicator, since it might be stale
|
|
47
|
-
if (!i.committed) this.setUnreadNotification(true)
|
|
48
|
-
})
|
|
49
|
-
this.lazyLists.forEach(l => l !== list && list.update(this.readStatusMap))
|
|
50
|
-
return result
|
|
51
|
-
},
|
|
52
|
-
})
|
|
53
|
-
this.lazyLists.set(list.id, list)
|
|
54
|
-
return list
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
destroyLazyNotificationList(id: number) {
|
|
58
|
-
this.lazyLists.get(id)?.mute()
|
|
59
|
-
this.lazyLists.delete(id)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async checkUnread() {
|
|
63
|
-
window.clearInterval(this.pollingTimeoutId)
|
|
64
|
-
this.setUnreadNotification(await this.config.checkForUnreadNotificationsSince(this.lastUnreadVerification))
|
|
65
|
-
this.lastUnreadVerification = new Date()
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async markAsCommitted(notificationId: string) {
|
|
69
|
-
if (this.readStatusMap.get(notificationId)) return
|
|
70
|
-
this.readStatusMap.set(notificationId, true)
|
|
71
|
-
this.lazyLists.forEach(l => l.update(this.readStatusMap))
|
|
72
|
-
try {
|
|
73
|
-
await this.config.markAsCommitted(notificationId)
|
|
74
|
-
// update the notification indicator: this is an inconsistency, we should actually ask the API, but since it's an expensive
|
|
75
|
-
// operation in the backend, we won't, instead, we'll only check the notifications we have loaded in memory.
|
|
76
|
-
if (!this.hasUnreadNotificationInMemory()) this.setUnreadNotification(false)
|
|
77
|
-
} catch {
|
|
78
|
-
this.readStatusMap.set(notificationId, false)
|
|
79
|
-
this.lazyLists.forEach(l => l.update(this.readStatusMap))
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
onUnreadNotificationChange(listener: UnreadNotificationListener) {
|
|
84
|
-
this.unreadNotificationListeners.push(listener)
|
|
85
|
-
return () => {
|
|
86
|
-
pull(this.unreadNotificationListeners, listener)
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
hasUnreadNotification() {
|
|
91
|
-
return this.unreadNotification
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
reset() {
|
|
95
|
-
this.lazyLists.clear()
|
|
96
|
-
this.readStatusMap.clear()
|
|
97
|
-
this.lastUnreadVerification = undefined
|
|
98
|
-
this.unreadNotification = false
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
mute() {
|
|
102
|
-
this.unreadNotificationListeners = []
|
|
103
|
-
}
|
|
104
|
-
}
|
|
1
|
+
import { pull } from 'lodash'
|
|
2
|
+
import { LazyNotificationList } from './LazyNotificationList'
|
|
3
|
+
import { LoadNotificationsFilters, NotificationConfig, UnreadNotificationListener } from './types'
|
|
4
|
+
|
|
5
|
+
const DEFAULT_POLLING_MS = 120000
|
|
6
|
+
|
|
7
|
+
export class NotificationController {
|
|
8
|
+
private lazyLists = new Map<number, LazyNotificationList>()
|
|
9
|
+
readonly config: NotificationConfig
|
|
10
|
+
private readStatusMap = new Map<string, boolean>()
|
|
11
|
+
private nextId = 1
|
|
12
|
+
private unreadNotification = false
|
|
13
|
+
private lastUnreadVerification: Date | undefined
|
|
14
|
+
private pollingTimeoutId: number | undefined
|
|
15
|
+
private unreadNotificationListeners: UnreadNotificationListener[] = []
|
|
16
|
+
|
|
17
|
+
constructor(config: NotificationConfig) {
|
|
18
|
+
this.config = config
|
|
19
|
+
config.pollingMS ??= DEFAULT_POLLING_MS
|
|
20
|
+
config.notificationsPath ??= '/notifications'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private hasUnreadNotificationInMemory() {
|
|
24
|
+
for (const [, read] of this.readStatusMap) {
|
|
25
|
+
if (!read.valueOf()) return true
|
|
26
|
+
}
|
|
27
|
+
return false
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private setUnreadNotification(value: boolean) {
|
|
31
|
+
this.unreadNotification = value
|
|
32
|
+
if (!value) {
|
|
33
|
+
this.pollingTimeoutId = window.setTimeout(() => this.checkUnread(), this.config.pollingMS)
|
|
34
|
+
}
|
|
35
|
+
this.unreadNotificationListeners.forEach(l => l(value))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
createLazyNotificationList(filters?: LoadNotificationsFilters): LazyNotificationList {
|
|
39
|
+
const list = new LazyNotificationList({
|
|
40
|
+
id: this.nextId++,
|
|
41
|
+
filters,
|
|
42
|
+
load: async (options) => {
|
|
43
|
+
const result = await this.config.load(options)
|
|
44
|
+
result.items.forEach((i) => {
|
|
45
|
+
this.readStatusMap.set(i.id, i.committed)
|
|
46
|
+
// updating the unread indicator, since it might be stale
|
|
47
|
+
if (!i.committed) this.setUnreadNotification(true)
|
|
48
|
+
})
|
|
49
|
+
this.lazyLists.forEach(l => l !== list && list.update(this.readStatusMap))
|
|
50
|
+
return result
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
this.lazyLists.set(list.id, list)
|
|
54
|
+
return list
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
destroyLazyNotificationList(id: number) {
|
|
58
|
+
this.lazyLists.get(id)?.mute()
|
|
59
|
+
this.lazyLists.delete(id)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async checkUnread() {
|
|
63
|
+
window.clearInterval(this.pollingTimeoutId)
|
|
64
|
+
this.setUnreadNotification(await this.config.checkForUnreadNotificationsSince(this.lastUnreadVerification))
|
|
65
|
+
this.lastUnreadVerification = new Date()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async markAsCommitted(notificationId: string) {
|
|
69
|
+
if (this.readStatusMap.get(notificationId)) return
|
|
70
|
+
this.readStatusMap.set(notificationId, true)
|
|
71
|
+
this.lazyLists.forEach(l => l.update(this.readStatusMap))
|
|
72
|
+
try {
|
|
73
|
+
await this.config.markAsCommitted(notificationId)
|
|
74
|
+
// update the notification indicator: this is an inconsistency, we should actually ask the API, but since it's an expensive
|
|
75
|
+
// operation in the backend, we won't, instead, we'll only check the notifications we have loaded in memory.
|
|
76
|
+
if (!this.hasUnreadNotificationInMemory()) this.setUnreadNotification(false)
|
|
77
|
+
} catch {
|
|
78
|
+
this.readStatusMap.set(notificationId, false)
|
|
79
|
+
this.lazyLists.forEach(l => l.update(this.readStatusMap))
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
onUnreadNotificationChange(listener: UnreadNotificationListener) {
|
|
84
|
+
this.unreadNotificationListeners.push(listener)
|
|
85
|
+
return () => {
|
|
86
|
+
pull(this.unreadNotificationListeners, listener)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
hasUnreadNotification() {
|
|
91
|
+
return this.unreadNotification
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
reset() {
|
|
95
|
+
this.lazyLists.clear()
|
|
96
|
+
this.readStatusMap.clear()
|
|
97
|
+
this.lastUnreadVerification = undefined
|
|
98
|
+
this.unreadNotification = false
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
mute() {
|
|
102
|
+
this.unreadNotificationListeners = []
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import { createContext, useContext } from 'react'
|
|
2
|
-
import { NotificationController } from './NotificationController'
|
|
3
|
-
|
|
4
|
-
export interface Props {
|
|
5
|
-
children: React.ReactNode,
|
|
6
|
-
controller: NotificationController,
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const context = createContext<NotificationController | undefined>(undefined)
|
|
10
|
-
|
|
11
|
-
export const NotificationProvider = ({ children, controller }: Props) => (
|
|
12
|
-
<context.Provider value={controller}>{children}</context.Provider>
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
export function useNotificationController() {
|
|
16
|
-
const controller = useContext(context)
|
|
17
|
-
if (!controller) {
|
|
18
|
-
throw new Error(
|
|
19
|
-
'A NotificationController was requested, but no NotificationController is available in the React Context. Please, be sure to wrap your component in a NotificationProvider.',
|
|
20
|
-
)
|
|
21
|
-
}
|
|
22
|
-
return controller
|
|
23
|
-
}
|
|
1
|
+
import { createContext, useContext } from 'react'
|
|
2
|
+
import { NotificationController } from './NotificationController'
|
|
3
|
+
|
|
4
|
+
export interface Props {
|
|
5
|
+
children: React.ReactNode,
|
|
6
|
+
controller: NotificationController,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const context = createContext<NotificationController | undefined>(undefined)
|
|
10
|
+
|
|
11
|
+
export const NotificationProvider = ({ children, controller }: Props) => (
|
|
12
|
+
<context.Provider value={controller}>{children}</context.Provider>
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
export function useNotificationController() {
|
|
16
|
+
const controller = useContext(context)
|
|
17
|
+
if (!controller) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
'A NotificationController was requested, but no NotificationController is available in the React Context. Please, be sure to wrap your component in a NotificationProvider.',
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
return controller
|
|
23
|
+
}
|
|
@@ -1,98 +1,98 @@
|
|
|
1
|
-
import { useLanguage } from '@stack-spot/portal-translate'
|
|
2
|
-
import { useCallback, useRef, useState } from 'react'
|
|
3
|
-
import { useManualRender } from '../../hooks/manual-render'
|
|
4
|
-
import { useEffectOnce } from '../../hooks/use-effect-once'
|
|
5
|
-
import { GetTenantNotificationsResponse } from '../../notifications'
|
|
6
|
-
import { useNotificationController } from './context'
|
|
7
|
-
import { LazyNotificationList } from './LazyNotificationList'
|
|
8
|
-
import { LoadNotificationsFilters } from './types'
|
|
9
|
-
|
|
10
|
-
export function useNotificationList(initialFilters: LoadNotificationsFilters = {}) {
|
|
11
|
-
const { repaint } = useManualRender()
|
|
12
|
-
const [status, setStatus] = useState<'startup' | 'idle' | 'error' | 'loading'>('startup')
|
|
13
|
-
const error = useRef<any>()
|
|
14
|
-
const controller = useNotificationController()
|
|
15
|
-
const [filters, setFilters] = useState(initialFilters)
|
|
16
|
-
const list = useRef<LazyNotificationList | undefined>()
|
|
17
|
-
|
|
18
|
-
useEffectOnce(() => {
|
|
19
|
-
async function start() {
|
|
20
|
-
list.current = controller.createLazyNotificationList(initialFilters)
|
|
21
|
-
list.current.subscribe(repaint)
|
|
22
|
-
try {
|
|
23
|
-
await list.current?.loadMore()
|
|
24
|
-
setStatus('idle')
|
|
25
|
-
} catch (e) {
|
|
26
|
-
setStatus('error')
|
|
27
|
-
error.current = e
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
start()
|
|
31
|
-
return () => {
|
|
32
|
-
if (list.current) controller.destroyLazyNotificationList(list.current.id)
|
|
33
|
-
}
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
const applyFilters = useCallback(async (newFilters: LoadNotificationsFilters) => {
|
|
37
|
-
if (!list.current) return
|
|
38
|
-
setStatus('loading')
|
|
39
|
-
// since we're inside a useCallback, we don't have direct access to the current value of "filters". The only way we can access this
|
|
40
|
-
// value is from the function "setFilters". But "setFilters" run async. For this reason, we wait the function to run and retrieve the
|
|
41
|
-
// current value before continuing, hence the use of the await and promise below.
|
|
42
|
-
const { next, prev } = await new Promise<{ next: LoadNotificationsFilters, prev: LoadNotificationsFilters }>((resolve) => {
|
|
43
|
-
setFilters((filters) => {
|
|
44
|
-
const prev = filters
|
|
45
|
-
const next = { ...filters, ...newFilters }
|
|
46
|
-
resolve({ prev, next })
|
|
47
|
-
return next
|
|
48
|
-
})
|
|
49
|
-
})
|
|
50
|
-
try {
|
|
51
|
-
await list.current.applyFilters(next)
|
|
52
|
-
setStatus('idle')
|
|
53
|
-
error.current = undefined
|
|
54
|
-
} catch (e) {
|
|
55
|
-
setFilters(prev)
|
|
56
|
-
if (error.current) {
|
|
57
|
-
error.current = e
|
|
58
|
-
setStatus('error')
|
|
59
|
-
} else {
|
|
60
|
-
setStatus('idle')
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}, [])
|
|
64
|
-
|
|
65
|
-
return {
|
|
66
|
-
status,
|
|
67
|
-
items: list.current?.items ?? [],
|
|
68
|
-
hasMore: list.current?.hasMore() ?? false,
|
|
69
|
-
loadMore: () => list.current?.loadMore(),
|
|
70
|
-
refresh: () => applyFilters(filters),
|
|
71
|
-
applyFilters,
|
|
72
|
-
filters,
|
|
73
|
-
error: error.current,
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function useGetNotificationTitleAndDescription({ content }: GetTenantNotificationsResponse) {
|
|
78
|
-
const language = useLanguage()
|
|
79
|
-
const { title, description } = content?.[language] || content?.en || {}
|
|
80
|
-
|
|
81
|
-
return {
|
|
82
|
-
title,
|
|
83
|
-
description,
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export function useUnreadNotifications() {
|
|
88
|
-
const [hasUnreadNotifications, setUnreadNotifications] = useState(false)
|
|
89
|
-
const controller = useNotificationController()
|
|
90
|
-
|
|
91
|
-
useEffectOnce(() => {
|
|
92
|
-
const unsubscribe = controller.onUnreadNotificationChange(setUnreadNotifications)
|
|
93
|
-
controller.checkUnread()
|
|
94
|
-
return unsubscribe
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
return hasUnreadNotifications
|
|
98
|
-
}
|
|
1
|
+
import { useLanguage } from '@stack-spot/portal-translate'
|
|
2
|
+
import { useCallback, useRef, useState } from 'react'
|
|
3
|
+
import { useManualRender } from '../../hooks/manual-render'
|
|
4
|
+
import { useEffectOnce } from '../../hooks/use-effect-once'
|
|
5
|
+
import { GetTenantNotificationsResponse } from '../../notifications'
|
|
6
|
+
import { useNotificationController } from './context'
|
|
7
|
+
import { LazyNotificationList } from './LazyNotificationList'
|
|
8
|
+
import { LoadNotificationsFilters } from './types'
|
|
9
|
+
|
|
10
|
+
export function useNotificationList(initialFilters: LoadNotificationsFilters = {}) {
|
|
11
|
+
const { repaint } = useManualRender()
|
|
12
|
+
const [status, setStatus] = useState<'startup' | 'idle' | 'error' | 'loading'>('startup')
|
|
13
|
+
const error = useRef<any>()
|
|
14
|
+
const controller = useNotificationController()
|
|
15
|
+
const [filters, setFilters] = useState(initialFilters)
|
|
16
|
+
const list = useRef<LazyNotificationList | undefined>()
|
|
17
|
+
|
|
18
|
+
useEffectOnce(() => {
|
|
19
|
+
async function start() {
|
|
20
|
+
list.current = controller.createLazyNotificationList(initialFilters)
|
|
21
|
+
list.current.subscribe(repaint)
|
|
22
|
+
try {
|
|
23
|
+
await list.current?.loadMore()
|
|
24
|
+
setStatus('idle')
|
|
25
|
+
} catch (e) {
|
|
26
|
+
setStatus('error')
|
|
27
|
+
error.current = e
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
start()
|
|
31
|
+
return () => {
|
|
32
|
+
if (list.current) controller.destroyLazyNotificationList(list.current.id)
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const applyFilters = useCallback(async (newFilters: LoadNotificationsFilters) => {
|
|
37
|
+
if (!list.current) return
|
|
38
|
+
setStatus('loading')
|
|
39
|
+
// since we're inside a useCallback, we don't have direct access to the current value of "filters". The only way we can access this
|
|
40
|
+
// value is from the function "setFilters". But "setFilters" run async. For this reason, we wait the function to run and retrieve the
|
|
41
|
+
// current value before continuing, hence the use of the await and promise below.
|
|
42
|
+
const { next, prev } = await new Promise<{ next: LoadNotificationsFilters, prev: LoadNotificationsFilters }>((resolve) => {
|
|
43
|
+
setFilters((filters) => {
|
|
44
|
+
const prev = filters
|
|
45
|
+
const next = { ...filters, ...newFilters }
|
|
46
|
+
resolve({ prev, next })
|
|
47
|
+
return next
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
try {
|
|
51
|
+
await list.current.applyFilters(next)
|
|
52
|
+
setStatus('idle')
|
|
53
|
+
error.current = undefined
|
|
54
|
+
} catch (e) {
|
|
55
|
+
setFilters(prev)
|
|
56
|
+
if (error.current) {
|
|
57
|
+
error.current = e
|
|
58
|
+
setStatus('error')
|
|
59
|
+
} else {
|
|
60
|
+
setStatus('idle')
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}, [])
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
status,
|
|
67
|
+
items: list.current?.items ?? [],
|
|
68
|
+
hasMore: list.current?.hasMore() ?? false,
|
|
69
|
+
loadMore: () => list.current?.loadMore(),
|
|
70
|
+
refresh: () => applyFilters(filters),
|
|
71
|
+
applyFilters,
|
|
72
|
+
filters,
|
|
73
|
+
error: error.current,
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function useGetNotificationTitleAndDescription({ content }: GetTenantNotificationsResponse) {
|
|
78
|
+
const language = useLanguage()
|
|
79
|
+
const { title, description } = content?.[language] || content?.en || {}
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
title,
|
|
83
|
+
description,
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function useUnreadNotifications() {
|
|
88
|
+
const [hasUnreadNotifications, setUnreadNotifications] = useState(false)
|
|
89
|
+
const controller = useNotificationController()
|
|
90
|
+
|
|
91
|
+
useEffectOnce(() => {
|
|
92
|
+
const unsubscribe = controller.onUnreadNotificationChange(setUnreadNotifications)
|
|
93
|
+
controller.checkUnread()
|
|
94
|
+
return unsubscribe
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
return hasUnreadNotifications
|
|
98
|
+
}
|
|
@@ -1,65 +1,66 @@
|
|
|
1
|
-
import { GetTenantNotificationsResponse, ResponseModelGetTenantNotificationsResponse } from '../../notifications'
|
|
2
|
-
|
|
3
|
-
export type NotificationPriority = 'HIGH' | 'MEDIUM' | 'LOW'
|
|
4
|
-
|
|
5
|
-
export type NotificationContext = 'ACCOUNT' | 'STUDIO' | 'WORKSPACE'
|
|
6
|
-
|
|
7
|
-
export interface LoadNotificationsFilters {
|
|
8
|
-
committed?: boolean,
|
|
9
|
-
criticality?: NotificationPriority,
|
|
10
|
-
search?: string,
|
|
11
|
-
context?: NotificationContext,
|
|
12
|
-
size?: number,
|
|
13
|
-
isBanner?: boolean,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
*
|
|
27
|
-
* @
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
*
|
|
33
|
-
* @
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
*
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
*
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
*
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
1
|
+
import { GetTenantNotificationsResponse, ResponseModelGetTenantNotificationsResponse } from '../../notifications'
|
|
2
|
+
|
|
3
|
+
export type NotificationPriority = 'HIGH' | 'MEDIUM' | 'LOW'
|
|
4
|
+
|
|
5
|
+
export type NotificationContext = 'ACCOUNT' | 'STUDIO' | 'WORKSPACE'
|
|
6
|
+
|
|
7
|
+
export interface LoadNotificationsFilters {
|
|
8
|
+
committed?: boolean,
|
|
9
|
+
criticality?: NotificationPriority,
|
|
10
|
+
search?: string,
|
|
11
|
+
context?: NotificationContext,
|
|
12
|
+
size?: number,
|
|
13
|
+
isBanner?: boolean,
|
|
14
|
+
createdSince?: number,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface LoadNotificationsOptions extends LoadNotificationsFilters {
|
|
18
|
+
page: number,
|
|
19
|
+
isBanner?: boolean,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type LazyNotificationListener = (notifications: GetTenantNotificationsResponse[], hasMore: boolean) => void
|
|
23
|
+
|
|
24
|
+
export interface NotificationConfig {
|
|
25
|
+
/**
|
|
26
|
+
* Fetches the notifications according to the options passed as parameters.
|
|
27
|
+
* @param options page, size and filters.
|
|
28
|
+
* @returns a promise that resolves to a {@link ResponseModelGetTenantNotificationsResponse}.
|
|
29
|
+
*/
|
|
30
|
+
load(options: LoadNotificationsOptions): Promise<ResponseModelGetTenantNotificationsResponse>,
|
|
31
|
+
/**
|
|
32
|
+
* Check for unread notifications since the date passed as parameter.
|
|
33
|
+
* @param date the date to start the search from
|
|
34
|
+
* @returns a promise that resolves to true if there are unread notifications or false otherwise.
|
|
35
|
+
*/
|
|
36
|
+
checkForUnreadNotificationsSince(date?: Date): Promise<boolean>,
|
|
37
|
+
/**
|
|
38
|
+
* Marks the notification with the id passed as parameter as read (committed).
|
|
39
|
+
* @param id the id of the notification.
|
|
40
|
+
*/
|
|
41
|
+
markAsCommitted(id: string): Promise<void>,
|
|
42
|
+
/**
|
|
43
|
+
* How long (ms) we should wait before checking for unread notifications again.
|
|
44
|
+
* @default 120000
|
|
45
|
+
*/
|
|
46
|
+
pollingMS?: number,
|
|
47
|
+
/**
|
|
48
|
+
* Path to the notifications page.
|
|
49
|
+
* @default '/notifications'
|
|
50
|
+
*/
|
|
51
|
+
notificationsPath?: string,
|
|
52
|
+
/**
|
|
53
|
+
* A function to call whenever an action button of a notification is clicked. It receives the id of the notification as a parameter.
|
|
54
|
+
*
|
|
55
|
+
* Useful for adding a behavior other than committing the notification and redirecting to its action. Example: analytics.
|
|
56
|
+
*
|
|
57
|
+
* @param id the id of the notification.
|
|
58
|
+
*/
|
|
59
|
+
onClickAction?: (id: string) => void,
|
|
60
|
+
/**
|
|
61
|
+
* Function that is called whenever the "read more" button is clicked.
|
|
62
|
+
*/
|
|
63
|
+
onClickViewNotification?: () => void,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type UnreadNotificationListener = (hasUnreadNotification: boolean) => void
|
package/src/hooks/date.ts
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
import { Language, useLanguage } from '@stack-spot/portal-translate'
|
|
2
|
-
import { format } from 'date-fns'
|
|
3
|
-
import { ptBR } from 'date-fns/locale'
|
|
4
|
-
|
|
5
|
-
function formatDate(date: string, locale: Language, includeTime: boolean) {
|
|
6
|
-
const time = includeTime ? ' HH:mm' : ''
|
|
7
|
-
return locale === 'pt'
|
|
8
|
-
? format(new Date(date), `dd/MM/yyyy${time}`, { locale: ptBR })
|
|
9
|
-
: format(new Date(date), `MMMM dd yyyy${time}`)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function formatSimpleDate(date: string, locale: Language) {
|
|
13
|
-
|
|
14
|
-
return locale === 'pt'
|
|
15
|
-
? format(new Date(date), 'dd/MM/yyyy', { locale: ptBR })
|
|
16
|
-
: format(new Date(date), 'MM/dd/yyyy')
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* A utility for formatting dates.
|
|
21
|
-
* @param date
|
|
22
|
-
* @returns a date formatter object.
|
|
23
|
-
*/
|
|
24
|
-
export function useDateFormatter() {
|
|
25
|
-
const locale = useLanguage()
|
|
26
|
-
return {
|
|
27
|
-
formatDate: (date?: string) => date ? formatDate(date, locale, false) : undefined,
|
|
28
|
-
formatDateWithTime: (date?: string) => date ? formatDate(date, locale, true) : undefined,
|
|
29
|
-
formatSimpleDate: (date?: string) => date ? formatSimpleDate(date, locale) : undefined,
|
|
30
|
-
}
|
|
31
|
-
}
|
|
1
|
+
import { Language, useLanguage } from '@stack-spot/portal-translate'
|
|
2
|
+
import { format } from 'date-fns'
|
|
3
|
+
import { ptBR } from 'date-fns/locale'
|
|
4
|
+
|
|
5
|
+
function formatDate(date: string, locale: Language, includeTime: boolean) {
|
|
6
|
+
const time = includeTime ? ' HH:mm' : ''
|
|
7
|
+
return locale === 'pt'
|
|
8
|
+
? format(new Date(date), `dd/MM/yyyy${time}`, { locale: ptBR })
|
|
9
|
+
: format(new Date(date), `MMMM dd yyyy${time}`)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function formatSimpleDate(date: string, locale: Language) {
|
|
13
|
+
|
|
14
|
+
return locale === 'pt'
|
|
15
|
+
? format(new Date(date), 'dd/MM/yyyy', { locale: ptBR })
|
|
16
|
+
: format(new Date(date), 'MM/dd/yyyy')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A utility for formatting dates.
|
|
21
|
+
* @param date
|
|
22
|
+
* @returns a date formatter object.
|
|
23
|
+
*/
|
|
24
|
+
export function useDateFormatter() {
|
|
25
|
+
const locale = useLanguage()
|
|
26
|
+
return {
|
|
27
|
+
formatDate: (date?: string) => date ? formatDate(date, locale, false) : undefined,
|
|
28
|
+
formatDateWithTime: (date?: string) => date ? formatDate(date, locale, true) : undefined,
|
|
29
|
+
formatSimpleDate: (date?: string) => date ? formatSimpleDate(date, locale) : undefined,
|
|
30
|
+
}
|
|
31
|
+
}
|