parent
56252046c1
commit
1e0e31cf09
|
@ -200,6 +200,7 @@
|
||||||
"Manifest": "Manifest",
|
"Manifest": "Manifest",
|
||||||
"Manifesto": "Manifesto",
|
"Manifesto": "Manifesto",
|
||||||
"Many files, choose only one": "Many files, choose only one",
|
"Many files, choose only one": "Many files, choose only one",
|
||||||
|
"Mark as read": "Mark as read",
|
||||||
"Material card": "Material card",
|
"Material card": "Material card",
|
||||||
"Message": "Message",
|
"Message": "Message",
|
||||||
"More": "More",
|
"More": "More",
|
||||||
|
|
|
@ -209,6 +209,7 @@
|
||||||
"Manifest": "Манифест",
|
"Manifest": "Манифест",
|
||||||
"Manifesto": "Манифест",
|
"Manifesto": "Манифест",
|
||||||
"Many files, choose only one": "Много файлов, выберете один",
|
"Many files, choose only one": "Много файлов, выберете один",
|
||||||
|
"Mark as read": "Отметить прочитанным",
|
||||||
"Material card": "Карточка материала",
|
"Material card": "Карточка материала",
|
||||||
"Message": "Написать",
|
"Message": "Написать",
|
||||||
"More": "Ещё",
|
"More": "Ещё",
|
||||||
|
|
|
@ -75,6 +75,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.L {
|
||||||
|
height: 40px;
|
||||||
|
width: 40px;
|
||||||
|
min-width: 40px;
|
||||||
|
.letters {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.XL {
|
&.XL {
|
||||||
aspect-ratio: 1/1;
|
aspect-ratio: 1/1;
|
||||||
margin: 0 auto 1rem;
|
margin: 0 auto 1rem;
|
||||||
|
|
|
@ -53,7 +53,7 @@ type Props = {
|
||||||
onlyBubbleControls?: boolean
|
onlyBubbleControls?: boolean
|
||||||
controlsAlwaysVisible?: boolean
|
controlsAlwaysVisible?: boolean
|
||||||
autoFocus?: boolean
|
autoFocus?: boolean
|
||||||
isCancelButtonVisible: boolean
|
isCancelButtonVisible?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MAX_DESCRIPTION_LIMIT = 400
|
export const MAX_DESCRIPTION_LIMIT = 400
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
|
padding: 4rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
.NotificationView {
|
.NotificationView {
|
||||||
|
@include font-size(1.5rem);
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
height: 72px;
|
min-height: 72px;
|
||||||
margin-left: -16px;
|
margin-left: -16px;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
background-color: var(--yellow-50);
|
background-color: var(--yellow-50);
|
||||||
// TODO: check markup
|
// TODO: check markup
|
||||||
font-size: 15px;
|
|
||||||
// font-weight: 700;
|
// font-weight: 700;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 100ms;
|
transition: background-color 100ms;
|
||||||
|
max-width: 700px;
|
||||||
|
|
||||||
&.seen {
|
&.seen {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
|
|
@ -10,7 +10,6 @@ $transition-duration: 200ms;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 0;
|
width: 0;
|
||||||
z-index: 10000;
|
z-index: 10000;
|
||||||
background-color: rgb(0 0 0 / 0%);
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition:
|
transition:
|
||||||
background-color $transition-duration,
|
background-color $transition-duration,
|
||||||
|
@ -18,12 +17,49 @@ $transition-duration: 200ms;
|
||||||
|
|
||||||
.panel {
|
.panel {
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: #fff;
|
background-color: var(--background-color);
|
||||||
width: 700px;
|
width: 50%;
|
||||||
padding: 48px 96px 96px 48px;
|
height: 100%;
|
||||||
transform: translateX(100%);
|
transform: translateX(100%);
|
||||||
transition: transform $transition-duration;
|
transition: transform $transition-duration;
|
||||||
overflow-y: auto;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
@include font-size(2rem);
|
||||||
|
|
||||||
|
color: var(--black-500);
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 36px;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
padding: 16px 38px;
|
||||||
|
border-bottom: 1px solid var(--black-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
overflow-y: auto;
|
||||||
|
flex: 1;
|
||||||
|
padding: 0 38px 1rem;
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
@include font-size(1.2rem);
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
padding: 1rem;
|
||||||
|
color: var(--black-300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
padding: 24px 38px;
|
||||||
|
width: 100%;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
background: var(--background-color);
|
||||||
|
border-top: 1px solid var(--black-100);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.isOpened {
|
&.isOpened {
|
||||||
|
@ -39,16 +75,6 @@ $transition-duration: 200ms;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
|
||||||
// TODO: check markup
|
|
||||||
color: var(--black-500, #141414);
|
|
||||||
font-size: 32px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 700;
|
|
||||||
line-height: 36px;
|
|
||||||
margin-bottom: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.closeButton {
|
.closeButton {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
|
@ -4,10 +4,13 @@ import { useEscKeyDownHandler } from '../../utils/useEscKeyDownHandler'
|
||||||
import { useOutsideClickHandler } from '../../utils/useOutsideClickHandler'
|
import { useOutsideClickHandler } from '../../utils/useOutsideClickHandler'
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { Icon } from '../_shared/Icon'
|
import { Icon } from '../_shared/Icon'
|
||||||
import { createEffect, createMemo, For, Show } from 'solid-js'
|
import { createEffect, createMemo, createSignal, For, on, onCleanup, onMount, Show } from 'solid-js'
|
||||||
import { useNotifications } from '../../context/notifications'
|
import { PAGE_SIZE, useNotifications } from '../../context/notifications'
|
||||||
import { NotificationView } from './NotificationView'
|
import { NotificationView } from './NotificationView'
|
||||||
import { EmptyMessage } from './EmptyMessage'
|
import { EmptyMessage } from './EmptyMessage'
|
||||||
|
import { Button } from '../_shared/Button'
|
||||||
|
import throttle from 'just-throttle'
|
||||||
|
import { useSession } from '../../context/session'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
|
@ -39,8 +42,17 @@ const isEarlier = (date: Date) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NotificationsPanel = (props: Props) => {
|
export const NotificationsPanel = (props: Props) => {
|
||||||
|
const [isLoading, setIsLoading] = createSignal(false)
|
||||||
|
|
||||||
|
const { isAuthenticated } = useSession()
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const { sortedNotifications } = useNotifications()
|
const {
|
||||||
|
sortedNotifications,
|
||||||
|
unreadNotificationsCount,
|
||||||
|
loadedNotificationsCount,
|
||||||
|
totalNotificationsCount,
|
||||||
|
actions: { loadNotifications, markAllNotificationsAsRead }
|
||||||
|
} = useNotifications()
|
||||||
const handleHide = () => {
|
const handleHide = () => {
|
||||||
props.onClose()
|
props.onClose()
|
||||||
}
|
}
|
||||||
|
@ -91,6 +103,57 @@ export const NotificationsPanel = (props: Props) => {
|
||||||
return sortedNotifications().filter((notification) => isEarlier(new Date(notification.createdAt)))
|
return sortedNotifications().filter((notification) => isEarlier(new Date(notification.createdAt)))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const scrollContainerRef: { current: HTMLDivElement } = { current: null }
|
||||||
|
const loadNextPage = async () => {
|
||||||
|
await loadNotifications({ limit: PAGE_SIZE, offset: loadedNotificationsCount() })
|
||||||
|
if (loadedNotificationsCount() < totalNotificationsCount()) {
|
||||||
|
const hasMore = scrollContainerRef.current.scrollHeight <= scrollContainerRef.current.offsetHeight
|
||||||
|
|
||||||
|
if (hasMore) {
|
||||||
|
await loadNextPage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const handleScroll = async () => {
|
||||||
|
if (!scrollContainerRef.current || isLoading()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (totalNotificationsCount() === loadedNotificationsCount()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const isNearBottom =
|
||||||
|
scrollContainerRef.current.scrollHeight - scrollContainerRef.current.scrollTop <=
|
||||||
|
scrollContainerRef.current.clientHeight * 1.5
|
||||||
|
|
||||||
|
if (isNearBottom) {
|
||||||
|
setIsLoading(true)
|
||||||
|
await loadNextPage()
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const handleScrollThrottled = throttle(handleScroll, 50)
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
scrollContainerRef.current.addEventListener('scroll', handleScrollThrottled)
|
||||||
|
onCleanup(() => {
|
||||||
|
scrollContainerRef.current.removeEventListener('scroll', handleScrollThrottled)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
createEffect(
|
||||||
|
on(
|
||||||
|
() => isAuthenticated(),
|
||||||
|
async () => {
|
||||||
|
if (isAuthenticated()) {
|
||||||
|
setIsLoading(true)
|
||||||
|
await loadNextPage()
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.container, {
|
class={clsx(styles.container, {
|
||||||
|
@ -103,46 +166,72 @@ export const NotificationsPanel = (props: Props) => {
|
||||||
<Icon name="close" />
|
<Icon name="close" />
|
||||||
</div>
|
</div>
|
||||||
<div class={styles.title}>{t('Notifications')}</div>
|
<div class={styles.title}>{t('Notifications')}</div>
|
||||||
<Show when={sortedNotifications().length > 0} fallback={<EmptyMessage />}>
|
<div class={clsx('wide-container', styles.content)} ref={(el) => (scrollContainerRef.current = el)}>
|
||||||
<Show when={todayNotifications().length > 0}>
|
<Show
|
||||||
<div class={styles.periodTitle}>{t('today')}</div>
|
when={sortedNotifications().length > 0}
|
||||||
<For each={todayNotifications()}>
|
fallback={
|
||||||
{(notification) => (
|
<Show when={!isLoading()}>
|
||||||
<NotificationView
|
<EmptyMessage />
|
||||||
notification={notification}
|
</Show>
|
||||||
class={styles.notificationView}
|
}
|
||||||
onClick={handleNotificationViewClick}
|
>
|
||||||
dateTimeFormat={'ago'}
|
<div class="row position-relative">
|
||||||
/>
|
<div class="col-xs-24">
|
||||||
)}
|
<Show when={todayNotifications().length > 0}>
|
||||||
</For>
|
<div class={styles.periodTitle}>{t('today')}</div>
|
||||||
|
<For each={todayNotifications()}>
|
||||||
|
{(notification) => (
|
||||||
|
<NotificationView
|
||||||
|
notification={notification}
|
||||||
|
class={styles.notificationView}
|
||||||
|
onClick={handleNotificationViewClick}
|
||||||
|
dateTimeFormat={'ago'}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</Show>
|
||||||
|
<Show when={yesterdayNotifications().length > 0}>
|
||||||
|
<div class={styles.periodTitle}>{t('yesterday')}</div>
|
||||||
|
<For each={yesterdayNotifications()}>
|
||||||
|
{(notification) => (
|
||||||
|
<NotificationView
|
||||||
|
notification={notification}
|
||||||
|
class={styles.notificationView}
|
||||||
|
onClick={handleNotificationViewClick}
|
||||||
|
dateTimeFormat={'time'}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</Show>
|
||||||
|
<Show when={earlierNotifications().length > 0}>
|
||||||
|
<div class={styles.periodTitle}>{t('earlier')}</div>
|
||||||
|
<For each={earlierNotifications()}>
|
||||||
|
{(notification) => (
|
||||||
|
<NotificationView
|
||||||
|
notification={notification}
|
||||||
|
class={styles.notificationView}
|
||||||
|
onClick={handleNotificationViewClick}
|
||||||
|
dateTimeFormat={'date'}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={yesterdayNotifications().length > 0}>
|
<Show when={isLoading()}>
|
||||||
<div class={styles.periodTitle}>{t('yesterday')}</div>
|
<div class={styles.loading}>{t('Loading')}</div>
|
||||||
<For each={yesterdayNotifications()}>
|
|
||||||
{(notification) => (
|
|
||||||
<NotificationView
|
|
||||||
notification={notification}
|
|
||||||
class={styles.notificationView}
|
|
||||||
onClick={handleNotificationViewClick}
|
|
||||||
dateTimeFormat={'time'}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</Show>
|
|
||||||
<Show when={earlierNotifications().length > 0}>
|
|
||||||
<div class={styles.periodTitle}>{t('earlier')}</div>
|
|
||||||
<For each={earlierNotifications()}>
|
|
||||||
{(notification) => (
|
|
||||||
<NotificationView
|
|
||||||
notification={notification}
|
|
||||||
class={styles.notificationView}
|
|
||||||
onClick={handleNotificationViewClick}
|
|
||||||
dateTimeFormat={'date'}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</Show>
|
</Show>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Show when={unreadNotificationsCount() > 0}>
|
||||||
|
<div class={styles.actions}>
|
||||||
|
<Button
|
||||||
|
onClick={() => markAllNotificationsAsRead()}
|
||||||
|
variant="secondary"
|
||||||
|
value={t('Mark as read')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { createEffect, createMemo, createSignal, For, onMount, Show } from 'solid-js'
|
import { createMemo, createSignal, For, onMount, Show } from 'solid-js'
|
||||||
import Banner from '../Discours/Banner'
|
import Banner from '../Discours/Banner'
|
||||||
import { Topics } from '../Nav/Topics'
|
import { Topics } from '../Nav/Topics'
|
||||||
import { Row5 } from '../Feed/Row5'
|
import { Row5 } from '../Feed/Row5'
|
||||||
|
|
|
@ -14,7 +14,7 @@ export const GroupAvatar = (props: Props) => {
|
||||||
const avatarSize = () => {
|
const avatarSize = () => {
|
||||||
switch (props.authors.length) {
|
switch (props.authors.length) {
|
||||||
case 1: {
|
case 1: {
|
||||||
return 'L'
|
return 'M'
|
||||||
}
|
}
|
||||||
case 2: {
|
case 2: {
|
||||||
return 'S'
|
return 'S'
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { createMemo, splitProps } from 'solid-js'
|
import { splitProps } from 'solid-js'
|
||||||
import type { JSX } from 'solid-js'
|
import type { JSX } from 'solid-js'
|
||||||
import { getImageUrl } from '../../../utils/getImageUrl'
|
import { getImageUrl } from '../../../utils/getImageUrl'
|
||||||
|
|
||||||
|
|
|
@ -14,13 +14,18 @@ type NotificationsContextType = {
|
||||||
notificationEntities: Record<number, Notification>
|
notificationEntities: Record<number, Notification>
|
||||||
unreadNotificationsCount: Accessor<number>
|
unreadNotificationsCount: Accessor<number>
|
||||||
sortedNotifications: Accessor<Notification[]>
|
sortedNotifications: Accessor<Notification[]>
|
||||||
|
loadedNotificationsCount: Accessor<number>
|
||||||
|
totalNotificationsCount: Accessor<number>
|
||||||
actions: {
|
actions: {
|
||||||
showNotificationsPanel: () => void
|
showNotificationsPanel: () => void
|
||||||
hideNotificationsPanel: () => void
|
hideNotificationsPanel: () => void
|
||||||
markNotificationAsRead: (notification: Notification) => Promise<void>
|
markNotificationAsRead: (notification: Notification) => Promise<void>
|
||||||
|
markAllNotificationsAsRead: () => Promise<void>
|
||||||
|
loadNotifications: (options: { limit: number; offset: number }) => Promise<Notification[]>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const PAGE_SIZE = 20
|
||||||
const NotificationsContext = createContext<NotificationsContextType>()
|
const NotificationsContext = createContext<NotificationsContextType>()
|
||||||
|
|
||||||
export function useNotifications() {
|
export function useNotifications() {
|
||||||
|
@ -32,18 +37,18 @@ const sseService = new SSEService()
|
||||||
export const NotificationsProvider = (props: { children: JSX.Element }) => {
|
export const NotificationsProvider = (props: { children: JSX.Element }) => {
|
||||||
const [isNotificationsPanelOpen, setIsNotificationsPanelOpen] = createSignal(false)
|
const [isNotificationsPanelOpen, setIsNotificationsPanelOpen] = createSignal(false)
|
||||||
const [unreadNotificationsCount, setUnreadNotificationsCount] = createSignal(0)
|
const [unreadNotificationsCount, setUnreadNotificationsCount] = createSignal(0)
|
||||||
|
const [totalNotificationsCount, setTotalNotificationsCount] = createSignal(0)
|
||||||
const { isAuthenticated, user } = useSession()
|
const { isAuthenticated, user } = useSession()
|
||||||
const [notificationEntities, setNotificationEntities] = createStore<Record<number, Notification>>({})
|
const [notificationEntities, setNotificationEntities] = createStore<Record<number, Notification>>({})
|
||||||
|
|
||||||
const loadNotifications = async () => {
|
const loadNotifications = async (options: { limit: number; offset?: number }) => {
|
||||||
const { notifications, totalUnreadCount } = await apiClient.getNotifications({
|
const { notifications, totalUnreadCount, totalCount } = await apiClient.getNotifications(options)
|
||||||
limit: 100
|
|
||||||
})
|
|
||||||
const newNotificationEntities = notifications.reduce((acc, notification) => {
|
const newNotificationEntities = notifications.reduce((acc, notification) => {
|
||||||
acc[notification.id] = notification
|
acc[notification.id] = notification
|
||||||
return acc
|
return acc
|
||||||
}, {})
|
}, {})
|
||||||
|
|
||||||
|
setTotalNotificationsCount(totalCount)
|
||||||
setUnreadNotificationsCount(totalUnreadCount)
|
setUnreadNotificationsCount(totalUnreadCount)
|
||||||
setNotificationEntities(newNotificationEntities)
|
setNotificationEntities(newNotificationEntities)
|
||||||
return notifications
|
return notifications
|
||||||
|
@ -55,14 +60,13 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const loadedNotificationsCount = createMemo(() => Object.keys(notificationEntities).length)
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (isAuthenticated()) {
|
if (isAuthenticated()) {
|
||||||
loadNotifications()
|
|
||||||
|
|
||||||
sseService.connect(`${apiBaseUrl}/subscribe/${user().id}`)
|
sseService.connect(`${apiBaseUrl}/subscribe/${user().id}`)
|
||||||
sseService.subscribeToEvent('message', (data: EventData) => {
|
sseService.subscribeToEvent('message', (data: EventData) => {
|
||||||
if (data.type === 'newNotifications') {
|
if (data.type === 'newNotifications') {
|
||||||
loadNotifications()
|
loadNotifications({ limit: loadedNotificationsCount() })
|
||||||
} else {
|
} else {
|
||||||
console.error(`[NotificationsProvider] unknown message type: ${JSON.stringify(data)}`)
|
console.error(`[NotificationsProvider] unknown message type: ${JSON.stringify(data)}`)
|
||||||
}
|
}
|
||||||
|
@ -74,7 +78,10 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
|
||||||
|
|
||||||
const markNotificationAsRead = async (notification: Notification) => {
|
const markNotificationAsRead = async (notification: Notification) => {
|
||||||
await apiClient.markNotificationAsRead(notification.id)
|
await apiClient.markNotificationAsRead(notification.id)
|
||||||
loadNotifications()
|
}
|
||||||
|
const markAllNotificationsAsRead = async () => {
|
||||||
|
await apiClient.markAllNotificationsAsRead()
|
||||||
|
loadNotifications({ limit: loadedNotificationsCount() })
|
||||||
}
|
}
|
||||||
|
|
||||||
const showNotificationsPanel = () => {
|
const showNotificationsPanel = () => {
|
||||||
|
@ -85,12 +92,20 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
|
||||||
setIsNotificationsPanelOpen(false)
|
setIsNotificationsPanelOpen(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const actions = { showNotificationsPanel, hideNotificationsPanel, markNotificationAsRead }
|
const actions = {
|
||||||
|
showNotificationsPanel,
|
||||||
|
hideNotificationsPanel,
|
||||||
|
markNotificationAsRead,
|
||||||
|
markAllNotificationsAsRead,
|
||||||
|
loadNotifications
|
||||||
|
}
|
||||||
|
|
||||||
const value: NotificationsContextType = {
|
const value: NotificationsContextType = {
|
||||||
notificationEntities,
|
notificationEntities,
|
||||||
sortedNotifications,
|
sortedNotifications,
|
||||||
unreadNotificationsCount,
|
unreadNotificationsCount,
|
||||||
|
loadedNotificationsCount,
|
||||||
|
totalNotificationsCount,
|
||||||
actions
|
actions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
9
src/graphql/mutation/mark-all-notifications-as-read.ts
Normal file
9
src/graphql/mutation/mark-all-notifications-as-read.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import { gql } from '@urql/core'
|
||||||
|
|
||||||
|
export default gql`
|
||||||
|
mutation MarkAllNotificationsAsReadMutation {
|
||||||
|
markAllNotificationsAsRead {
|
||||||
|
error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
|
@ -1,8 +1,8 @@
|
||||||
import { gql } from '@urql/core'
|
import { gql } from '@urql/core'
|
||||||
|
|
||||||
export default gql`
|
export default gql`
|
||||||
query LoadNotificationsQuery {
|
query LoadNotificationsQuery($params: NotificationsQueryParams!) {
|
||||||
loadNotifications(params: { limit: 10, offset: 0 }) {
|
loadNotifications(params: $params) {
|
||||||
notifications {
|
notifications {
|
||||||
id
|
id
|
||||||
shout
|
shout
|
||||||
|
|
|
@ -59,6 +59,7 @@ import updateArticle from '../graphql/mutation/article-update'
|
||||||
import deleteShout from '../graphql/mutation/article-delete'
|
import deleteShout from '../graphql/mutation/article-delete'
|
||||||
import notifications from '../graphql/query/notifications'
|
import notifications from '../graphql/query/notifications'
|
||||||
import markNotificationAsRead from '../graphql/mutation/mark-notification-as-read'
|
import markNotificationAsRead from '../graphql/mutation/mark-notification-as-read'
|
||||||
|
import markAllNotificationsAsRead from '../graphql/mutation/mark-all-notifications-as-read'
|
||||||
import mySubscriptions from '../graphql/query/my-subscriptions'
|
import mySubscriptions from '../graphql/query/my-subscriptions'
|
||||||
|
|
||||||
type ApiErrorCode =
|
type ApiErrorCode =
|
||||||
|
@ -352,12 +353,10 @@ export const apiClient = {
|
||||||
const resp = await publicGraphQLClient
|
const resp = await publicGraphQLClient
|
||||||
.query(reactionsLoadBy, { by, limit: limit ?? 1000, offset: 0 })
|
.query(reactionsLoadBy, { by, limit: limit ?? 1000, offset: 0 })
|
||||||
.toPromise()
|
.toPromise()
|
||||||
// console.debug(resp)
|
|
||||||
return resp.data.loadReactionsBy
|
return resp.data.loadReactionsBy
|
||||||
},
|
},
|
||||||
getNotifications: async (params: NotificationsQueryParams): Promise<NotificationsQueryResult> => {
|
getNotifications: async (params: NotificationsQueryParams): Promise<NotificationsQueryResult> => {
|
||||||
const resp = await privateGraphQLClient.query(notifications, params).toPromise()
|
const resp = await privateGraphQLClient.query(notifications, { params }).toPromise()
|
||||||
// console.debug(resp.data)
|
|
||||||
return resp.data.loadNotifications
|
return resp.data.loadNotifications
|
||||||
},
|
},
|
||||||
markNotificationAsRead: async (notificationId: number): Promise<void> => {
|
markNotificationAsRead: async (notificationId: number): Promise<void> => {
|
||||||
|
@ -368,6 +367,10 @@ export const apiClient = {
|
||||||
.toPromise()
|
.toPromise()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
markAllNotificationsAsRead: async (): Promise<void> => {
|
||||||
|
await privateGraphQLClient.mutation(markAllNotificationsAsRead, {}).toPromise()
|
||||||
|
},
|
||||||
|
|
||||||
getMySubscriptions: async (): Promise<MySubscriptionsQueryResult> => {
|
getMySubscriptions: async (): Promise<MySubscriptionsQueryResult> => {
|
||||||
const resp = await privateGraphQLClient.query(mySubscriptions, {}).toPromise()
|
const resp = await privateGraphQLClient.query(mySubscriptions, {}).toPromise()
|
||||||
// console.debug(resp.data)
|
// console.debug(resp.data)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user