@stack-spot/portal-components 2.26.0 → 2.27.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.
- package/CHANGELOG.md +621 -614
- 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 +3 -3
- package/dist/components/Placeholder.js +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.js +1 -1
- package/dist/components/notification/NotificationList.d.ts +1 -1
- package/dist/components/notification/NotificationList.js +43 -43
- package/dist/components/notification/NotificationPlaceholder.d.ts +1 -1
- package/dist/components/notification/NotificationPlaceholder.js +9 -9
- package/dist/components/notification/NotificationPlaceholder.js.map +1 -1
- package/dist/containers/NotificationsPage.d.ts +1 -1
- package/dist/containers/NotificationsPage.js +10 -10
- 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/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 +1 -1
- package/dist/svg/GenericPlaceholder.js +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 +118 -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 +336 -336
- package/src/components/notification/NotificationList.tsx +178 -178
- package/src/components/notification/NotificationPlaceholder.tsx +43 -43
- package/src/components/notification/types.ts +72 -72
- package/src/containers/NotificationsPage.tsx +98 -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 +65 -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,336 +1,336 @@
|
|
|
1
|
-
import { Box, Button, Flex, IconBox, OneOfColorSchemesWithVariants, Styles, SxProp, Text } from '@citric/core'
|
|
2
|
-
import { Envelope, EnvelopeOpen, ExternalLink } from '@citric/icons'
|
|
3
|
-
import { IconButton, Tooltip } from '@citric/ui'
|
|
4
|
-
import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
|
|
5
|
-
import { differenceInDays, parseISO } from 'date-fns'
|
|
6
|
-
import { useAnchorTag } from '../../context/anchor'
|
|
7
|
-
import { useGetNotificationTitleAndDescription } from '../../context/notification/hooks'
|
|
8
|
-
import { useDateFormatter } from '../../hooks/date'
|
|
9
|
-
import { LazyMarkdown } from '../LazyMarkdown'
|
|
10
|
-
import { GetTenantNotificationsResponse } from './types'
|
|
11
|
-
|
|
12
|
-
const styles = {
|
|
13
|
-
item: (color: string, isRead: boolean) => ({
|
|
14
|
-
borderLeft: `2px solid ${color}`,
|
|
15
|
-
opacity: isRead ? 0.5 : 1,
|
|
16
|
-
}),
|
|
17
|
-
} satisfies Record<string, (...args: any) => SxProp>
|
|
18
|
-
|
|
19
|
-
const statusToColor: Record<string, OneOfColorSchemesWithVariants> = {
|
|
20
|
-
LOW: 'success.500',
|
|
21
|
-
MEDIUM: 'warning.500',
|
|
22
|
-
HIGH: 'danger.500',
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface Props {
|
|
26
|
-
notification: GetTenantNotificationsResponse,
|
|
27
|
-
isSummary: boolean,
|
|
28
|
-
// @deprecated this property currently does nothing. Remove in next major.
|
|
29
|
-
id?: string,
|
|
30
|
-
onClickViewNotification?: () => void,
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const style: Styles = {
|
|
34
|
-
notificationDescription: {
|
|
35
|
-
display: '-webkit-box',
|
|
36
|
-
WebkitBoxOrient: 'vertical',
|
|
37
|
-
overflow: 'hidden',
|
|
38
|
-
WebkitLineClamp: '2',
|
|
39
|
-
lineHeight: '1.5',
|
|
40
|
-
maxHeight: '3em',
|
|
41
|
-
maxWidth: '340px',
|
|
42
|
-
},
|
|
43
|
-
notificationTooltip: {
|
|
44
|
-
overflowWrap: 'break-word',
|
|
45
|
-
},
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* NotificationHeader component that renders the header of a notification.
|
|
50
|
-
*
|
|
51
|
-
* @param props the component's props.
|
|
52
|
-
*/
|
|
53
|
-
const NotificationHeader = ({ title, isSummary }: { title: string, isSummary: boolean }) => (
|
|
54
|
-
<Flex justifyContent="space-between" mb={2} sx={{ maxWidth: isSummary ? '330px' : '100%' }} flexWrap="nowrap">
|
|
55
|
-
<Text appearance={isSummary ? 'body2' : 'body1'} weight="medium" nowrapEllipsis mr={5}>
|
|
56
|
-
{title}
|
|
57
|
-
</Text>
|
|
58
|
-
</Flex>
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Get the number of days ago from the given date string.
|
|
63
|
-
*
|
|
64
|
-
* @param {string} dateString - The date string to calculate the days ago.
|
|
65
|
-
* @returns {number} The number of days ago.
|
|
66
|
-
*/
|
|
67
|
-
const getDaysAgo = (dateString: string): number => {
|
|
68
|
-
const givenDate = parseISO(dateString)
|
|
69
|
-
const today = new Date()
|
|
70
|
-
return differenceInDays(today, givenDate)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* NotificationContent component that renders the content of a notification.
|
|
75
|
-
*
|
|
76
|
-
* @param props the component's props {@link Props}.
|
|
77
|
-
*/
|
|
78
|
-
const NotificationContent = ({ notification, isSummary, onClickViewNotification }: Props) => {
|
|
79
|
-
const { formatDate } = useDateFormatter()
|
|
80
|
-
const Link = useAnchorTag()
|
|
81
|
-
|
|
82
|
-
const t = useTranslate(dictionary)
|
|
83
|
-
const { description } = useGetNotificationTitleAndDescription(notification)
|
|
84
|
-
const daysAgo = getDaysAgo(notification.trigger_at)
|
|
85
|
-
|
|
86
|
-
const components = () => ({
|
|
87
|
-
ul: (props: any) => (
|
|
88
|
-
<Text
|
|
89
|
-
as="ul"
|
|
90
|
-
appearance="body2"
|
|
91
|
-
colorScheme="light.700"
|
|
92
|
-
sx={{ listStyleType: 'disc', marginLeft: '2rem', marginBottom: '1em' }}
|
|
93
|
-
{...props}
|
|
94
|
-
/>
|
|
95
|
-
),
|
|
96
|
-
li: (props: any) => (
|
|
97
|
-
<Text
|
|
98
|
-
as="li"
|
|
99
|
-
appearance="body2"
|
|
100
|
-
colorScheme="light.700"
|
|
101
|
-
sx={{ marginBottom: '0.5em' }}
|
|
102
|
-
{...props}
|
|
103
|
-
/>
|
|
104
|
-
),
|
|
105
|
-
p: (props: any) => (
|
|
106
|
-
<Text
|
|
107
|
-
as="p"
|
|
108
|
-
appearance="body2"
|
|
109
|
-
sx={{ marginBottom: '1em' }}
|
|
110
|
-
{...props}
|
|
111
|
-
/>
|
|
112
|
-
),
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
const navigateToNotificationPosition = (ev: React.MouseEvent<HTMLElement>, anchorText: string) => {
|
|
116
|
-
ev.preventDefault()
|
|
117
|
-
|
|
118
|
-
const checkElement = setInterval(() => {
|
|
119
|
-
const target = document.getElementById(anchorText)
|
|
120
|
-
if (target) {
|
|
121
|
-
target.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
122
|
-
clearInterval(checkElement)
|
|
123
|
-
}
|
|
124
|
-
}, 100)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return (
|
|
128
|
-
<>
|
|
129
|
-
{!isSummary && <Flex mt={4} mb={3} flexDirection="column">
|
|
130
|
-
<LazyMarkdown components={components()}>{description}</LazyMarkdown>
|
|
131
|
-
</Flex>}
|
|
132
|
-
{isSummary && (
|
|
133
|
-
<Text appearance="body2" mb={3} sx={style.notificationDescription}>
|
|
134
|
-
{handleTextMark(description)}
|
|
135
|
-
</Text>
|
|
136
|
-
)}
|
|
137
|
-
<Flex justifyContent="space-between">
|
|
138
|
-
<Flex>
|
|
139
|
-
<Text appearance="microtext1" colorScheme="light.700">
|
|
140
|
-
{t[notification.context]}
|
|
141
|
-
</Text>
|
|
142
|
-
<Text appearance="microtext1" colorScheme="light.700" mx="2">
|
|
143
|
-
•
|
|
144
|
-
</Text>
|
|
145
|
-
{!isSummary && <>
|
|
146
|
-
<Text appearance="microtext1" colorScheme="light.700">
|
|
147
|
-
{formatDate(notification.trigger_at)}
|
|
148
|
-
</Text>
|
|
149
|
-
<Text appearance="microtext1" colorScheme="light.700" mx="2">
|
|
150
|
-
•
|
|
151
|
-
</Text>
|
|
152
|
-
</>}
|
|
153
|
-
<Text appearance="microtext1" colorScheme="light.700">
|
|
154
|
-
{daysAgo > 0 ? daysAgo + ' ' + t.daysAgo : t.today}
|
|
155
|
-
</Text>
|
|
156
|
-
</Flex>
|
|
157
|
-
{isSummary && description.length > 120 && (
|
|
158
|
-
<Link
|
|
159
|
-
href="/notifications"
|
|
160
|
-
onClick={(ev: React.MouseEvent<HTMLElement>) => {
|
|
161
|
-
onClickViewNotification?.()
|
|
162
|
-
navigateToNotificationPosition(ev, `notification-item-${notification.id}`)}}>
|
|
163
|
-
<Text appearance="microtext1" colorScheme="light.700">{t.readMore}</Text>
|
|
164
|
-
</Link>
|
|
165
|
-
)}
|
|
166
|
-
</Flex>
|
|
167
|
-
</>
|
|
168
|
-
)
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
interface NotificationFooterProps {
|
|
172
|
-
/**
|
|
173
|
-
* The URL to open when the action button is clicked.
|
|
174
|
-
*/
|
|
175
|
-
actionURL: string,
|
|
176
|
-
/**
|
|
177
|
-
* Function to call when the user clicks the action button.
|
|
178
|
-
*/
|
|
179
|
-
onClickAction: () => void,
|
|
180
|
-
/**
|
|
181
|
-
* Notification title
|
|
182
|
-
*/
|
|
183
|
-
title?: string,
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* NotificationFooter component that renders the footer of a notification.
|
|
188
|
-
*
|
|
189
|
-
* @param props the component's props {@link NotificationFooterProps}.
|
|
190
|
-
*/
|
|
191
|
-
const NotificationFooter = ({ actionURL, onClickAction, title }: NotificationFooterProps) => {
|
|
192
|
-
const t = useTranslate(dictionary)
|
|
193
|
-
const Link = useAnchorTag()
|
|
194
|
-
|
|
195
|
-
return (
|
|
196
|
-
<Flex mt="5">
|
|
197
|
-
<Button
|
|
198
|
-
size="sm"
|
|
199
|
-
colorScheme="inverse"
|
|
200
|
-
appearance="text"
|
|
201
|
-
onClick={onClickAction}
|
|
202
|
-
as={Link}
|
|
203
|
-
href={actionURL}
|
|
204
|
-
aria-label={t.viewNotification.replace('%s', title || t.view)}
|
|
205
|
-
target="_blank"
|
|
206
|
-
>
|
|
207
|
-
<Text sx={{ mr: 2, textDecoration: 'underline' }}>
|
|
208
|
-
{t.view}
|
|
209
|
-
</Text>
|
|
210
|
-
<IconBox size="xs">
|
|
211
|
-
<ExternalLink />
|
|
212
|
-
</IconBox>
|
|
213
|
-
</Button>
|
|
214
|
-
</Flex>
|
|
215
|
-
)
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
interface DeprecatedNotificationItemProps extends Props {
|
|
219
|
-
/**
|
|
220
|
-
* @deprecated use `onCommit` and `onClickAction` instead.
|
|
221
|
-
*
|
|
222
|
-
* Function to call when the user marks a notification as read or unread. This can happen either through the mail icon or when the user
|
|
223
|
-
* clicks the action button of a notification. Type will be "icon" on the first case or "callToAction" on the former.
|
|
224
|
-
*
|
|
225
|
-
* Tip: clicking the action button (callToAction) should only change the read status if the message is unread. This is not treated by this
|
|
226
|
-
* component, be sure to handle it on your side.
|
|
227
|
-
*
|
|
228
|
-
* Deprecation warning: since the deprecation, the parameter `read` is always true.
|
|
229
|
-
*/
|
|
230
|
-
onClickMarkReadUnread: (read: boolean, type: 'callToAction' | 'icon') => void | Promise<void>,
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
interface NewNotificationItemProps extends Props {
|
|
234
|
-
/**
|
|
235
|
-
* Function to call when the user marks a notification as read (committed).
|
|
236
|
-
*/
|
|
237
|
-
onCommit: () => void,
|
|
238
|
-
/**
|
|
239
|
-
* Whenever the user clicks the action button of a notification, the notification is marked as read (committed) and the browser is
|
|
240
|
-
* redirected to the page that corresponds to the action.
|
|
241
|
-
*
|
|
242
|
-
* If you need additional behavior, use this parameter, which is a function to call whenever the button is clicked.
|
|
243
|
-
*/
|
|
244
|
-
onClickAction?: () => void,
|
|
245
|
-
/**
|
|
246
|
-
* Function to call when the user clicks in read more button.
|
|
247
|
-
*/
|
|
248
|
-
onClickViewNotification?: () => void,
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
type NotificationItemProps = DeprecatedNotificationItemProps | NewNotificationItemProps
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* NotificationItem component that renders a notification item.
|
|
255
|
-
*
|
|
256
|
-
* @param props the component's props {@link NotificationItemProps}.
|
|
257
|
-
*/
|
|
258
|
-
export const NotificationItem = ({ notification, isSummary, ...props }: NotificationItemProps) => {
|
|
259
|
-
const t = useTranslate(dictionary)
|
|
260
|
-
const { title } = useGetNotificationTitleAndDescription(notification)
|
|
261
|
-
|
|
262
|
-
function commit() {
|
|
263
|
-
if ('onCommit' in props) props.onCommit()
|
|
264
|
-
else props.onClickMarkReadUnread(true, 'icon')
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function clickAction() {
|
|
268
|
-
if ('onCommit' in props) {
|
|
269
|
-
if (!notification.committed) props.onCommit()
|
|
270
|
-
props.onClickAction?.()
|
|
271
|
-
}
|
|
272
|
-
else props.onClickMarkReadUnread(true, 'callToAction')
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
return (
|
|
276
|
-
<Box sx={{ position: 'relative' }} id={props.id}>
|
|
277
|
-
<Flex bg="light.400" p="3 3 3 5" r="xs"
|
|
278
|
-
flexWrap="nowrap"
|
|
279
|
-
flexDirection="column" w="100%" sx={styles.item(statusToColor[notification.criticality], notification.committed)}>
|
|
280
|
-
<NotificationHeader title={title} isSummary={isSummary} />
|
|
281
|
-
<NotificationContent notification={notification} isSummary={isSummary} onClickViewNotification={props.onClickViewNotification} />
|
|
282
|
-
{notification.call_to_action && <NotificationFooter
|
|
283
|
-
actionURL={notification.call_to_action}
|
|
284
|
-
onClickAction={clickAction}
|
|
285
|
-
title={title}
|
|
286
|
-
/>}
|
|
287
|
-
</Flex>
|
|
288
|
-
<Box sx={{ position: 'absolute', top: '8px', right: '8px' }}>
|
|
289
|
-
<Tooltip text={notification.committed ? t.committed : t.uncommitted} position="left">
|
|
290
|
-
{notification.committed
|
|
291
|
-
? <IconBox role="img" aria-label={t.committed} size="xs" style={{ margin: '4px', opacity: 0.5 }}><EnvelopeOpen /></IconBox>
|
|
292
|
-
: (
|
|
293
|
-
<IconButton aria-label={t.uncommitted} onClick={commit}>
|
|
294
|
-
<IconBox size="xs"><Envelope /></IconBox>
|
|
295
|
-
</IconButton>
|
|
296
|
-
)
|
|
297
|
-
}
|
|
298
|
-
</Tooltip>
|
|
299
|
-
</Box>
|
|
300
|
-
</Box>
|
|
301
|
-
)
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
function handleTextMark(text: string) {
|
|
305
|
-
// eslint-disable-next-line no-useless-escape
|
|
306
|
-
return text.replace(/[^\p{L}\p{N}\s\p{Emoji_Presentation}:!'()"\/_-]/gu, '')
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const dictionary = {
|
|
310
|
-
en: {
|
|
311
|
-
ACCOUNT: 'Account',
|
|
312
|
-
WORKSPACE: 'Workspace',
|
|
313
|
-
STUDIO: 'Studio',
|
|
314
|
-
AI: 'AI',
|
|
315
|
-
daysAgo: 'days ago',
|
|
316
|
-
today: 'today',
|
|
317
|
-
committed: 'This notification has been read.',
|
|
318
|
-
uncommitted: 'This notification has not been read yet. Click to mark as read.',
|
|
319
|
-
view: 'View',
|
|
320
|
-
viewNotification: 'View notification %s',
|
|
321
|
-
readMore: 'Read more',
|
|
322
|
-
},
|
|
323
|
-
pt: {
|
|
324
|
-
ACCOUNT: 'Conta',
|
|
325
|
-
WORKSPACE: 'Workspace',
|
|
326
|
-
STUDIO: 'Estúdio',
|
|
327
|
-
AI: 'AI',
|
|
328
|
-
daysAgo: 'dias atrás',
|
|
329
|
-
today: 'hoje',
|
|
330
|
-
committed: 'Esta notificação já foi lida.',
|
|
331
|
-
uncommitted: 'Esta notificação ainda não foi lida. Clique para marcar como lida.',
|
|
332
|
-
view: 'Visualizar',
|
|
333
|
-
viewNotification: 'Visualizar notificação %s',
|
|
334
|
-
readMore: 'Ler mais',
|
|
335
|
-
},
|
|
336
|
-
} satisfies Dictionary
|
|
1
|
+
import { Box, Button, Flex, IconBox, OneOfColorSchemesWithVariants, Styles, SxProp, Text } from '@citric/core'
|
|
2
|
+
import { Envelope, EnvelopeOpen, ExternalLink } from '@citric/icons'
|
|
3
|
+
import { IconButton, Tooltip } from '@citric/ui'
|
|
4
|
+
import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
|
|
5
|
+
import { differenceInDays, parseISO } from 'date-fns'
|
|
6
|
+
import { useAnchorTag } from '../../context/anchor'
|
|
7
|
+
import { useGetNotificationTitleAndDescription } from '../../context/notification/hooks'
|
|
8
|
+
import { useDateFormatter } from '../../hooks/date'
|
|
9
|
+
import { LazyMarkdown } from '../LazyMarkdown'
|
|
10
|
+
import { GetTenantNotificationsResponse } from './types'
|
|
11
|
+
|
|
12
|
+
const styles = {
|
|
13
|
+
item: (color: string, isRead: boolean) => ({
|
|
14
|
+
borderLeft: `2px solid ${color}`,
|
|
15
|
+
opacity: isRead ? 0.5 : 1,
|
|
16
|
+
}),
|
|
17
|
+
} satisfies Record<string, (...args: any) => SxProp>
|
|
18
|
+
|
|
19
|
+
const statusToColor: Record<string, OneOfColorSchemesWithVariants> = {
|
|
20
|
+
LOW: 'success.500',
|
|
21
|
+
MEDIUM: 'warning.500',
|
|
22
|
+
HIGH: 'danger.500',
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface Props {
|
|
26
|
+
notification: GetTenantNotificationsResponse,
|
|
27
|
+
isSummary: boolean,
|
|
28
|
+
// @deprecated this property currently does nothing. Remove in next major.
|
|
29
|
+
id?: string,
|
|
30
|
+
onClickViewNotification?: () => void,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const style: Styles = {
|
|
34
|
+
notificationDescription: {
|
|
35
|
+
display: '-webkit-box',
|
|
36
|
+
WebkitBoxOrient: 'vertical',
|
|
37
|
+
overflow: 'hidden',
|
|
38
|
+
WebkitLineClamp: '2',
|
|
39
|
+
lineHeight: '1.5',
|
|
40
|
+
maxHeight: '3em',
|
|
41
|
+
maxWidth: '340px',
|
|
42
|
+
},
|
|
43
|
+
notificationTooltip: {
|
|
44
|
+
overflowWrap: 'break-word',
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* NotificationHeader component that renders the header of a notification.
|
|
50
|
+
*
|
|
51
|
+
* @param props the component's props.
|
|
52
|
+
*/
|
|
53
|
+
const NotificationHeader = ({ title, isSummary }: { title: string, isSummary: boolean }) => (
|
|
54
|
+
<Flex justifyContent="space-between" mb={2} sx={{ maxWidth: isSummary ? '330px' : '100%' }} flexWrap="nowrap">
|
|
55
|
+
<Text appearance={isSummary ? 'body2' : 'body1'} weight="medium" nowrapEllipsis mr={5}>
|
|
56
|
+
{title}
|
|
57
|
+
</Text>
|
|
58
|
+
</Flex>
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Get the number of days ago from the given date string.
|
|
63
|
+
*
|
|
64
|
+
* @param {string} dateString - The date string to calculate the days ago.
|
|
65
|
+
* @returns {number} The number of days ago.
|
|
66
|
+
*/
|
|
67
|
+
const getDaysAgo = (dateString: string): number => {
|
|
68
|
+
const givenDate = parseISO(dateString)
|
|
69
|
+
const today = new Date()
|
|
70
|
+
return differenceInDays(today, givenDate)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* NotificationContent component that renders the content of a notification.
|
|
75
|
+
*
|
|
76
|
+
* @param props the component's props {@link Props}.
|
|
77
|
+
*/
|
|
78
|
+
const NotificationContent = ({ notification, isSummary, onClickViewNotification }: Props) => {
|
|
79
|
+
const { formatDate } = useDateFormatter()
|
|
80
|
+
const Link = useAnchorTag()
|
|
81
|
+
|
|
82
|
+
const t = useTranslate(dictionary)
|
|
83
|
+
const { description } = useGetNotificationTitleAndDescription(notification)
|
|
84
|
+
const daysAgo = getDaysAgo(notification.trigger_at)
|
|
85
|
+
|
|
86
|
+
const components = () => ({
|
|
87
|
+
ul: (props: any) => (
|
|
88
|
+
<Text
|
|
89
|
+
as="ul"
|
|
90
|
+
appearance="body2"
|
|
91
|
+
colorScheme="light.700"
|
|
92
|
+
sx={{ listStyleType: 'disc', marginLeft: '2rem', marginBottom: '1em' }}
|
|
93
|
+
{...props}
|
|
94
|
+
/>
|
|
95
|
+
),
|
|
96
|
+
li: (props: any) => (
|
|
97
|
+
<Text
|
|
98
|
+
as="li"
|
|
99
|
+
appearance="body2"
|
|
100
|
+
colorScheme="light.700"
|
|
101
|
+
sx={{ marginBottom: '0.5em' }}
|
|
102
|
+
{...props}
|
|
103
|
+
/>
|
|
104
|
+
),
|
|
105
|
+
p: (props: any) => (
|
|
106
|
+
<Text
|
|
107
|
+
as="p"
|
|
108
|
+
appearance="body2"
|
|
109
|
+
sx={{ marginBottom: '1em' }}
|
|
110
|
+
{...props}
|
|
111
|
+
/>
|
|
112
|
+
),
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
const navigateToNotificationPosition = (ev: React.MouseEvent<HTMLElement>, anchorText: string) => {
|
|
116
|
+
ev.preventDefault()
|
|
117
|
+
|
|
118
|
+
const checkElement = setInterval(() => {
|
|
119
|
+
const target = document.getElementById(anchorText)
|
|
120
|
+
if (target) {
|
|
121
|
+
target.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
122
|
+
clearInterval(checkElement)
|
|
123
|
+
}
|
|
124
|
+
}, 100)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<>
|
|
129
|
+
{!isSummary && <Flex mt={4} mb={3} flexDirection="column">
|
|
130
|
+
<LazyMarkdown components={components()}>{description}</LazyMarkdown>
|
|
131
|
+
</Flex>}
|
|
132
|
+
{isSummary && (
|
|
133
|
+
<Text appearance="body2" mb={3} sx={style.notificationDescription}>
|
|
134
|
+
{handleTextMark(description)}
|
|
135
|
+
</Text>
|
|
136
|
+
)}
|
|
137
|
+
<Flex justifyContent="space-between">
|
|
138
|
+
<Flex>
|
|
139
|
+
<Text appearance="microtext1" colorScheme="light.700">
|
|
140
|
+
{t[notification.context]}
|
|
141
|
+
</Text>
|
|
142
|
+
<Text appearance="microtext1" colorScheme="light.700" mx="2">
|
|
143
|
+
•
|
|
144
|
+
</Text>
|
|
145
|
+
{!isSummary && <>
|
|
146
|
+
<Text appearance="microtext1" colorScheme="light.700">
|
|
147
|
+
{formatDate(notification.trigger_at)}
|
|
148
|
+
</Text>
|
|
149
|
+
<Text appearance="microtext1" colorScheme="light.700" mx="2">
|
|
150
|
+
•
|
|
151
|
+
</Text>
|
|
152
|
+
</>}
|
|
153
|
+
<Text appearance="microtext1" colorScheme="light.700">
|
|
154
|
+
{daysAgo > 0 ? daysAgo + ' ' + t.daysAgo : t.today}
|
|
155
|
+
</Text>
|
|
156
|
+
</Flex>
|
|
157
|
+
{isSummary && description.length > 120 && (
|
|
158
|
+
<Link
|
|
159
|
+
href="/notifications"
|
|
160
|
+
onClick={(ev: React.MouseEvent<HTMLElement>) => {
|
|
161
|
+
onClickViewNotification?.()
|
|
162
|
+
navigateToNotificationPosition(ev, `notification-item-${notification.id}`)}}>
|
|
163
|
+
<Text appearance="microtext1" colorScheme="light.700">{t.readMore}</Text>
|
|
164
|
+
</Link>
|
|
165
|
+
)}
|
|
166
|
+
</Flex>
|
|
167
|
+
</>
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
interface NotificationFooterProps {
|
|
172
|
+
/**
|
|
173
|
+
* The URL to open when the action button is clicked.
|
|
174
|
+
*/
|
|
175
|
+
actionURL: string,
|
|
176
|
+
/**
|
|
177
|
+
* Function to call when the user clicks the action button.
|
|
178
|
+
*/
|
|
179
|
+
onClickAction: () => void,
|
|
180
|
+
/**
|
|
181
|
+
* Notification title
|
|
182
|
+
*/
|
|
183
|
+
title?: string,
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* NotificationFooter component that renders the footer of a notification.
|
|
188
|
+
*
|
|
189
|
+
* @param props the component's props {@link NotificationFooterProps}.
|
|
190
|
+
*/
|
|
191
|
+
const NotificationFooter = ({ actionURL, onClickAction, title }: NotificationFooterProps) => {
|
|
192
|
+
const t = useTranslate(dictionary)
|
|
193
|
+
const Link = useAnchorTag()
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<Flex mt="5">
|
|
197
|
+
<Button
|
|
198
|
+
size="sm"
|
|
199
|
+
colorScheme="inverse"
|
|
200
|
+
appearance="text"
|
|
201
|
+
onClick={onClickAction}
|
|
202
|
+
as={Link}
|
|
203
|
+
href={actionURL}
|
|
204
|
+
aria-label={t.viewNotification.replace('%s', title || t.view)}
|
|
205
|
+
target="_blank"
|
|
206
|
+
>
|
|
207
|
+
<Text sx={{ mr: 2, textDecoration: 'underline' }}>
|
|
208
|
+
{t.view}
|
|
209
|
+
</Text>
|
|
210
|
+
<IconBox size="xs">
|
|
211
|
+
<ExternalLink />
|
|
212
|
+
</IconBox>
|
|
213
|
+
</Button>
|
|
214
|
+
</Flex>
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
interface DeprecatedNotificationItemProps extends Props {
|
|
219
|
+
/**
|
|
220
|
+
* @deprecated use `onCommit` and `onClickAction` instead.
|
|
221
|
+
*
|
|
222
|
+
* Function to call when the user marks a notification as read or unread. This can happen either through the mail icon or when the user
|
|
223
|
+
* clicks the action button of a notification. Type will be "icon" on the first case or "callToAction" on the former.
|
|
224
|
+
*
|
|
225
|
+
* Tip: clicking the action button (callToAction) should only change the read status if the message is unread. This is not treated by this
|
|
226
|
+
* component, be sure to handle it on your side.
|
|
227
|
+
*
|
|
228
|
+
* Deprecation warning: since the deprecation, the parameter `read` is always true.
|
|
229
|
+
*/
|
|
230
|
+
onClickMarkReadUnread: (read: boolean, type: 'callToAction' | 'icon') => void | Promise<void>,
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
interface NewNotificationItemProps extends Props {
|
|
234
|
+
/**
|
|
235
|
+
* Function to call when the user marks a notification as read (committed).
|
|
236
|
+
*/
|
|
237
|
+
onCommit: () => void,
|
|
238
|
+
/**
|
|
239
|
+
* Whenever the user clicks the action button of a notification, the notification is marked as read (committed) and the browser is
|
|
240
|
+
* redirected to the page that corresponds to the action.
|
|
241
|
+
*
|
|
242
|
+
* If you need additional behavior, use this parameter, which is a function to call whenever the button is clicked.
|
|
243
|
+
*/
|
|
244
|
+
onClickAction?: () => void,
|
|
245
|
+
/**
|
|
246
|
+
* Function to call when the user clicks in read more button.
|
|
247
|
+
*/
|
|
248
|
+
onClickViewNotification?: () => void,
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
type NotificationItemProps = DeprecatedNotificationItemProps | NewNotificationItemProps
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* NotificationItem component that renders a notification item.
|
|
255
|
+
*
|
|
256
|
+
* @param props the component's props {@link NotificationItemProps}.
|
|
257
|
+
*/
|
|
258
|
+
export const NotificationItem = ({ notification, isSummary, ...props }: NotificationItemProps) => {
|
|
259
|
+
const t = useTranslate(dictionary)
|
|
260
|
+
const { title } = useGetNotificationTitleAndDescription(notification)
|
|
261
|
+
|
|
262
|
+
function commit() {
|
|
263
|
+
if ('onCommit' in props) props.onCommit()
|
|
264
|
+
else props.onClickMarkReadUnread(true, 'icon')
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function clickAction() {
|
|
268
|
+
if ('onCommit' in props) {
|
|
269
|
+
if (!notification.committed) props.onCommit()
|
|
270
|
+
props.onClickAction?.()
|
|
271
|
+
}
|
|
272
|
+
else props.onClickMarkReadUnread(true, 'callToAction')
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return (
|
|
276
|
+
<Box sx={{ position: 'relative' }} id={props.id}>
|
|
277
|
+
<Flex bg="light.400" p="3 3 3 5" r="xs"
|
|
278
|
+
flexWrap="nowrap"
|
|
279
|
+
flexDirection="column" w="100%" sx={styles.item(statusToColor[notification.criticality], notification.committed)}>
|
|
280
|
+
<NotificationHeader title={title} isSummary={isSummary} />
|
|
281
|
+
<NotificationContent notification={notification} isSummary={isSummary} onClickViewNotification={props.onClickViewNotification} />
|
|
282
|
+
{notification.call_to_action && <NotificationFooter
|
|
283
|
+
actionURL={notification.call_to_action}
|
|
284
|
+
onClickAction={clickAction}
|
|
285
|
+
title={title}
|
|
286
|
+
/>}
|
|
287
|
+
</Flex>
|
|
288
|
+
<Box sx={{ position: 'absolute', top: '8px', right: '8px' }}>
|
|
289
|
+
<Tooltip text={notification.committed ? t.committed : t.uncommitted} position="left">
|
|
290
|
+
{notification.committed
|
|
291
|
+
? <IconBox role="img" aria-label={t.committed} size="xs" style={{ margin: '4px', opacity: 0.5 }}><EnvelopeOpen /></IconBox>
|
|
292
|
+
: (
|
|
293
|
+
<IconButton aria-label={t.uncommitted} onClick={commit}>
|
|
294
|
+
<IconBox size="xs"><Envelope /></IconBox>
|
|
295
|
+
</IconButton>
|
|
296
|
+
)
|
|
297
|
+
}
|
|
298
|
+
</Tooltip>
|
|
299
|
+
</Box>
|
|
300
|
+
</Box>
|
|
301
|
+
)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function handleTextMark(text: string) {
|
|
305
|
+
// eslint-disable-next-line no-useless-escape
|
|
306
|
+
return text.replace(/[^\p{L}\p{N}\s\p{Emoji_Presentation}:!'()"\/_-]/gu, '')
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const dictionary = {
|
|
310
|
+
en: {
|
|
311
|
+
ACCOUNT: 'Account',
|
|
312
|
+
WORKSPACE: 'Workspace',
|
|
313
|
+
STUDIO: 'Studio',
|
|
314
|
+
AI: 'AI',
|
|
315
|
+
daysAgo: 'days ago',
|
|
316
|
+
today: 'today',
|
|
317
|
+
committed: 'This notification has been read.',
|
|
318
|
+
uncommitted: 'This notification has not been read yet. Click to mark as read.',
|
|
319
|
+
view: 'View',
|
|
320
|
+
viewNotification: 'View notification %s',
|
|
321
|
+
readMore: 'Read more',
|
|
322
|
+
},
|
|
323
|
+
pt: {
|
|
324
|
+
ACCOUNT: 'Conta',
|
|
325
|
+
WORKSPACE: 'Workspace',
|
|
326
|
+
STUDIO: 'Estúdio',
|
|
327
|
+
AI: 'AI',
|
|
328
|
+
daysAgo: 'dias atrás',
|
|
329
|
+
today: 'hoje',
|
|
330
|
+
committed: 'Esta notificação já foi lida.',
|
|
331
|
+
uncommitted: 'Esta notificação ainda não foi lida. Clique para marcar como lida.',
|
|
332
|
+
view: 'Visualizar',
|
|
333
|
+
viewNotification: 'Visualizar notificação %s',
|
|
334
|
+
readMore: 'Ler mais',
|
|
335
|
+
},
|
|
336
|
+
} satisfies Dictionary
|