From a39d000ff2a7ea54dbf63351affc80642a078229 Mon Sep 17 00:00:00 2001 From: muerwre <33246675+muerwre@users.noreply.github.com> Date: Sat, 11 Mar 2023 17:16:31 +0600 Subject: [PATCH] add user notifications (#148) * added notification settings * notifications: added list to profile * notifications: changed appearance for comment notifications --- src/api/notifications/settings.ts | 17 +++ src/api/notifications/types.ts | 13 +++ .../common/InlineUsername/index.tsx | 20 ++++ .../common/InlineUsername/styles.module.scss | 6 + .../notifications/NotificationBadge/index.tsx | 77 +++++++++++++ .../NotificationBadge/styles.module.scss | 33 ++++++ .../NotificationBubble/index.tsx | 47 -------- .../NotificationBubble/styles.module.scss | 106 ------------------ .../NotificationComment/index.tsx | 43 +++++++ .../NotificationComment/styles.module.scss | 52 +++++++++ .../NotificationMessage/index.tsx | 32 ------ .../ProfileSidebarNotes/styles.module.scss | 7 -- .../ProfileSidebarNotifications/index.tsx | 27 +++++ src/constants/api.ts | 4 + .../notifications/NotificationList/index.tsx | 34 ++++++ .../NotificationList/styles.module.scss | 41 +++++++ .../profile/ProfileSidebarMenu/index.tsx | 9 +- .../sidebars/ProfileSidebar/index.tsx | 4 +- .../notifications/useNotificationSettings.ts | 28 +++++ .../useNotificationSettingsRequest.ts | 36 ++++++ .../notifications/useNotificationsList.ts | 20 ++++ src/pages/_app.tsx | 43 +++---- src/styles/_mixins.scss | 8 ++ src/types/auth/index.ts | 6 + src/types/notifications/index.ts | 16 +++ src/utils/dom.ts | 10 +- src/utils/providers/NotificationProvider.tsx | 31 +++++ 27 files changed, 552 insertions(+), 218 deletions(-) create mode 100644 src/api/notifications/settings.ts create mode 100644 src/api/notifications/types.ts create mode 100644 src/components/common/InlineUsername/index.tsx create mode 100644 src/components/common/InlineUsername/styles.module.scss create mode 100644 src/components/notifications/NotificationBadge/index.tsx create mode 100644 src/components/notifications/NotificationBadge/styles.module.scss delete mode 100644 src/components/notifications/NotificationBubble/index.tsx delete mode 100644 src/components/notifications/NotificationBubble/styles.module.scss create mode 100644 src/components/notifications/NotificationComment/index.tsx create mode 100644 src/components/notifications/NotificationComment/styles.module.scss delete mode 100644 src/components/notifications/NotificationMessage/index.tsx delete mode 100644 src/components/profile/ProfileSidebarNotes/styles.module.scss create mode 100644 src/components/profile/ProfileSidebarNotifications/index.tsx create mode 100644 src/containers/notifications/NotificationList/index.tsx create mode 100644 src/containers/notifications/NotificationList/styles.module.scss create mode 100644 src/hooks/notifications/useNotificationSettings.ts create mode 100644 src/hooks/notifications/useNotificationSettingsRequest.ts create mode 100644 src/hooks/notifications/useNotificationsList.ts create mode 100644 src/types/notifications/index.ts create mode 100644 src/utils/providers/NotificationProvider.tsx diff --git a/src/api/notifications/settings.ts b/src/api/notifications/settings.ts new file mode 100644 index 00000000..8f670dac --- /dev/null +++ b/src/api/notifications/settings.ts @@ -0,0 +1,17 @@ +import { API } from '~/constants/api'; +import { api, cleanResult } from '~/utils/api'; + +import { + ApiGetNotificationSettingsResponse, + ApiGetNotificationsResponse, +} from './types'; + +export const apiGetNotificationSettings = () => + api + .get(API.NOTIFICATIONS.SETTINGS) + .then(cleanResult); + +export const apiGetNotifications = () => + api + .get(API.NOTIFICATIONS.LIST) + .then(cleanResult); diff --git a/src/api/notifications/types.ts b/src/api/notifications/types.ts new file mode 100644 index 00000000..b242fc76 --- /dev/null +++ b/src/api/notifications/types.ts @@ -0,0 +1,13 @@ +import { NotificationItem } from '~/types/notifications'; + +export interface ApiGetNotificationSettingsResponse { + enabled: boolean; + flow: boolean; + comments: boolean; + last_seen?: string | null; + last_date?: string | null; +} + +export interface ApiGetNotificationsResponse { + items?: NotificationItem[]; +} diff --git a/src/components/common/InlineUsername/index.tsx b/src/components/common/InlineUsername/index.tsx new file mode 100644 index 00000000..1bfb3124 --- /dev/null +++ b/src/components/common/InlineUsername/index.tsx @@ -0,0 +1,20 @@ +import React, { FC } from 'react'; + +import { useColorFromString } from '~/hooks/color/useColorFromString'; + +import styles from './styles.module.scss'; + +interface InlineUsernameProps { + children: string; +} + +const InlineUsername: FC = ({ children }) => { + const backgroundColor = useColorFromString(children); + return ( + + ~{children} + + ); +}; + +export { InlineUsername }; diff --git a/src/components/common/InlineUsername/styles.module.scss b/src/components/common/InlineUsername/styles.module.scss new file mode 100644 index 00000000..552c701a --- /dev/null +++ b/src/components/common/InlineUsername/styles.module.scss @@ -0,0 +1,6 @@ +.username { + font-size: 0.9em; + padding: 0 2px; + text-transform: lowercase; + border-radius: 0.2em; +} diff --git a/src/components/notifications/NotificationBadge/index.tsx b/src/components/notifications/NotificationBadge/index.tsx new file mode 100644 index 00000000..20c32a29 --- /dev/null +++ b/src/components/notifications/NotificationBadge/index.tsx @@ -0,0 +1,77 @@ +import React, { FC } from 'react'; + +import { Anchor } from '~/components/common/Anchor'; +import { InlineUsername } from '~/components/common/InlineUsername'; +import { Square } from '~/components/common/Square'; +import { Card } from '~/components/containers/Card'; +import { FlowRecentItem } from '~/components/flow/FlowRecentItem'; +import { NotificationItem, NotificationType } from '~/types/notifications'; +import { formatText, getPrettyDate, getURLFromString } from '~/utils/dom'; + +import styles from './styles.module.scss'; + +interface NotificationBadgeProps { + item: NotificationItem; +} + +const getTitle = (item: NotificationItem) => { + if (!item.user.username) { + return ''; + } + + switch (item.type) { + case NotificationType.Comment: + return ( + + {item.user.username} пишет: + + ); + case NotificationType.Node: + return ( + + Новый пост от {item.user.username}: + + ); + } +}; + +const getContent = (item: NotificationItem) => { + switch (item.type) { + case NotificationType.Comment: + return ( +
+ ); + case NotificationType.Node: + return ( +
+ ); + } +}; + +const getIcon = (item: NotificationItem) => { + return ; +}; + +const NotificationBadge: FC = ({ item }) => ( + +
+
{getIcon(item)}
+ +
+ {getTitle(item)} +
{getContent(item)}
+
{getPrettyDate(item.created_at)}
+
+
+
+); + +export { NotificationBadge }; diff --git a/src/components/notifications/NotificationBadge/styles.module.scss b/src/components/notifications/NotificationBadge/styles.module.scss new file mode 100644 index 00000000..8468237e --- /dev/null +++ b/src/components/notifications/NotificationBadge/styles.module.scss @@ -0,0 +1,33 @@ +@import 'src/styles/variables'; + +.link { + text-decoration: none; + color: inherit; +} + +.message { + font: $font_14_regular; + line-height: 1.3em; + padding: $gap/2 $gap/2 $gap/4 $gap/2; + min-height: calc(1.3em * 3 + $gap); + display: grid; + grid-template-columns: 40px auto; + column-gap: $gap; +} + +.text { + @include clamp(2, 14px); + text-overflow: ellipsis; +} + +.title { + font: $font_14_semibold; + margin-bottom: $gap / 2; +} + +.time { + font: $font_10_regular; + text-align: right; + margin-top: 2px; + color: $gray_75; +} diff --git a/src/components/notifications/NotificationBubble/index.tsx b/src/components/notifications/NotificationBubble/index.tsx deleted file mode 100644 index 0ef0d796..00000000 --- a/src/components/notifications/NotificationBubble/index.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React, { createElement, FC } from 'react'; - -import { Icon } from '~/components/input/Icon'; -import { useRandomPhrase } from '~/constants/phrases'; -import { INotification, NOTIFICATION_TYPES } from '~/types'; - -import { NotificationMessage } from '../NotificationMessage'; - -import styles from './styles.module.scss'; - -interface IProps { - notifications: INotification[]; - onClick: (notification: INotification) => void; -} - -const NOTIFICATION_RENDERERS = { - [NOTIFICATION_TYPES.message]: NotificationMessage, -}; - -const NotificationBubble: FC = ({ notifications, onClick }) => { - const placeholder = useRandomPhrase('NOTHING_HERE'); - - return ( -
-
- {notifications.length === 0 && ( -
- -
{placeholder}
-
- )} - {notifications.length > 0 && - notifications - .filter(notification => notification.type && NOTIFICATION_RENDERERS[notification.type]) - .map(notification => - createElement(NOTIFICATION_RENDERERS[notification.type], { - notification, - onClick, - key: notification.content.id, - }) - )} -
-
- ); -}; - -export { NotificationBubble }; diff --git a/src/components/notifications/NotificationBubble/styles.module.scss b/src/components/notifications/NotificationBubble/styles.module.scss deleted file mode 100644 index d351ef4d..00000000 --- a/src/components/notifications/NotificationBubble/styles.module.scss +++ /dev/null @@ -1,106 +0,0 @@ -@import 'src/styles/variables'; - -$notification_color: $content_bg_dark; - -@keyframes appear { - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } -} - -.wrap { - position: absolute; - background: $notification_color; - top: 42px; - left: 50%; - transform: translate(-50%, 0); - border-radius: $radius; - animation: appear 0.25s forwards; - z-index: 2; - - &::before { - content: ' '; - width: 0; - height: 0; - border-style: solid; - border-width: 0 0 16px 16px; - border-color: transparent transparent $notification_color transparent; - position: absolute; - left: 50%; - top: -16px; - transform: translate(-20px, 0); - } -} - -.list { - width: 300px; - max-width: 100vw; - min-width: 0; - max-height: 400px; - overflow: auto; -} - -.item { - display: flex; - align-items: stretch; - justify-content: stretch; - flex-direction: column; - padding: $gap; - min-width: 0; - cursor: pointer; - - svg { - fill: white; - margin-right: $gap; - } -} - -.item_head { - display: flex; - align-items: center; - justify-content: flex-start; - flex-direction: row; -} - -.item_title { - flex: 1; - white-space: nowrap; - font: $font_14_semibold; - overflow: hidden; - text-overflow: ellipsis; - // text-transform: none; -} - -.item_text { - font: $font_14_regular; - max-height: 2.4em; - padding-left: 30px; - overflow: hidden; -} - -.placeholder { - height: 200px; - display: flex; - align-items: center; - justify-content: center; - flex-direction: column; - text-transform: uppercase; - font: $font_16_semibold; - box-sizing: border-box; - padding: 80px; - text-align: center; - line-height: 1.6em; - - svg { - width: 120px; - height: 120px; - opacity: 0.05; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - } -} diff --git a/src/components/notifications/NotificationComment/index.tsx b/src/components/notifications/NotificationComment/index.tsx new file mode 100644 index 00000000..2f7c1b42 --- /dev/null +++ b/src/components/notifications/NotificationComment/index.tsx @@ -0,0 +1,43 @@ +import { FC } from 'react'; + +import { Anchor } from '~/components/common/Anchor'; +import { InlineUsername } from '~/components/common/InlineUsername'; +import { Square } from '~/components/common/Square'; +import { NotificationItem } from '~/types/notifications'; +import { formatText, getPrettyDate, getURLFromString } from '~/utils/dom'; + +import styles from './styles.module.scss'; + +interface NotificationCommentProps { + item: NotificationItem; +} + +const NotificationComment: FC = ({ item }) => ( + +
+
+ +
+ +
+ + + {item.user.username}: + + +
+
+
+
+
+ +); + +export { NotificationComment }; diff --git a/src/components/notifications/NotificationComment/styles.module.scss b/src/components/notifications/NotificationComment/styles.module.scss new file mode 100644 index 00000000..6d57a501 --- /dev/null +++ b/src/components/notifications/NotificationComment/styles.module.scss @@ -0,0 +1,52 @@ +@import 'src/styles/variables'; + +.link { + text-decoration: none; + color: inherit; +} + +.message { + font: $font_14_regular; + line-height: 1.3em; + padding: $gap/2 $gap/2 $gap/4 $gap/2; + min-height: calc(1.3em * 3 + $gap); + display: grid; + grid-template-columns: 40px auto; + column-gap: 0; +} + +.content { + background: $content_bg; + padding: 5px; + border-radius: 0 4px 4px 4px; + position: relative; + + &:before { + content: ' '; + position: absolute; + top: $gap; + right: 100%; + @include arrow_left(10px, $content_bg); + } +} + +.text { + @include clamp(2, 14px); + text-overflow: ellipsis; +} + +.title { + font: $font_14_semibold; + margin-bottom: $gap / 2; +} + +.time { + font: $font_10_regular; + text-align: right; + margin-top: 2px; + color: $gray_75; +} + +.circle { + border-radius: 4px 0 0 4px; +} diff --git a/src/components/notifications/NotificationMessage/index.tsx b/src/components/notifications/NotificationMessage/index.tsx deleted file mode 100644 index 760d9390..00000000 --- a/src/components/notifications/NotificationMessage/index.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React, { FC, useCallback } from 'react'; - -import { Icon } from '~/components/input/Icon'; -import styles from '~/components/notifications/NotificationBubble/styles.module.scss'; -import { IMessageNotification, INotification } from '~/types'; - -interface IProps { - notification: IMessageNotification; - onClick: (notification: INotification) => void; -} - -const NotificationMessage: FC = ({ - notification, - notification: { - content: { text, from }, - }, - onClick, -}) => { - const onMouseDown = useCallback(() => onClick(notification), [onClick, notification]); - - return ( -
-
- -
Сообщение от ~{from?.username}:
-
-
{text}
-
- ); -}; - -export { NotificationMessage }; diff --git a/src/components/profile/ProfileSidebarNotes/styles.module.scss b/src/components/profile/ProfileSidebarNotes/styles.module.scss deleted file mode 100644 index 078e0081..00000000 --- a/src/components/profile/ProfileSidebarNotes/styles.module.scss +++ /dev/null @@ -1,7 +0,0 @@ -@import "src/styles/variables"; - -.scroller { - flex: 1; - overflow: auto; - padding: $gap; -} diff --git a/src/components/profile/ProfileSidebarNotifications/index.tsx b/src/components/profile/ProfileSidebarNotifications/index.tsx new file mode 100644 index 00000000..07fc114c --- /dev/null +++ b/src/components/profile/ProfileSidebarNotifications/index.tsx @@ -0,0 +1,27 @@ +import { VFC } from 'react'; + +import { useStackContext } from '~/components/sidebar/SidebarStack'; +import { SidebarStackCard } from '~/components/sidebar/SidebarStackCard'; + +import { NotificationList } from '../../../containers/notifications/NotificationList/index'; + +interface ProfileSidebarNotificationsProps {} + +const ProfileSidebarNotifications: VFC< + ProfileSidebarNotificationsProps +> = () => { + const { closeAllTabs } = useStackContext(); + + return ( + + + + ); +}; + +export { ProfileSidebarNotifications }; diff --git a/src/constants/api.ts b/src/constants/api.ts index 6cb36bf4..fe16b3c5 100644 --- a/src/constants/api.ts +++ b/src/constants/api.ts @@ -62,4 +62,8 @@ export const API = { STATS: '/nodes/lab/stats', UPDATES: '/nodes/lab/updates', }, + NOTIFICATIONS: { + LIST: '/notifications/', + SETTINGS: '/notifications/settings', + }, }; diff --git a/src/containers/notifications/NotificationList/index.tsx b/src/containers/notifications/NotificationList/index.tsx new file mode 100644 index 00000000..f2860246 --- /dev/null +++ b/src/containers/notifications/NotificationList/index.tsx @@ -0,0 +1,34 @@ +import React, { FC } from 'react'; + +import { LoaderCircle } from '~/components/input/LoaderCircle'; +import { NotificationComment } from '~/components/notifications/NotificationComment'; +import { useNotificationsList } from '~/hooks/notifications/useNotificationsList'; + +import styles from './styles.module.scss'; + +interface NotificationListProps {} + +const NotificationList: FC = () => { + const { isLoading, items } = useNotificationsList(); + + if (isLoading) { + return ; + } + + return ( +
+ {/*
HEAD
*/} +
+
+ {items?.map((item) => ( +
+ +
+ ))} +
+
+
+ ); +}; + +export { NotificationList }; diff --git a/src/containers/notifications/NotificationList/styles.module.scss b/src/containers/notifications/NotificationList/styles.module.scss new file mode 100644 index 00000000..594c5406 --- /dev/null +++ b/src/containers/notifications/NotificationList/styles.module.scss @@ -0,0 +1,41 @@ +@import 'src/styles/variables'; + +.grid { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + z-index: 4; + height: 100%; +} + +.head { + @include row_shadow; + + width: 100%; + padding: $gap; +} + +.list { + @include row_shadow; + + overflow-y: auto; + flex: 1 1; + overflow: auto; + width: 100%; +} + +.items { + display: grid; + grid-auto-flow: row; +} + +.item { + @include row_shadow; + padding-right: $gap; + transition: background-color 0.25s; + + &:hover { + background-color: $content_bg_lighter; + } +} diff --git a/src/containers/profile/ProfileSidebarMenu/index.tsx b/src/containers/profile/ProfileSidebarMenu/index.tsx index 4cf8ae07..368e3174 100644 --- a/src/containers/profile/ProfileSidebarMenu/index.tsx +++ b/src/containers/profile/ProfileSidebarMenu/index.tsx @@ -2,6 +2,7 @@ import React, { useCallback, VFC } from 'react'; import classNames from 'classnames'; +import { Superpower } from '~/components/boris/Superpower'; import { Filler } from '~/components/containers/Filler'; import { Group } from '~/components/containers/Group'; import { Zone } from '~/components/containers/Zone'; @@ -44,7 +45,13 @@ const ProfileSidebarMenu: VFC = ({ onClose }) => { Настройки - setActiveTab(1)}> + + setActiveTab(1)}> + Уведомления + + + + setActiveTab(2)}> Заметки diff --git a/src/containers/sidebars/ProfileSidebar/index.tsx b/src/containers/sidebars/ProfileSidebar/index.tsx index 05ffaeb3..54c21597 100644 --- a/src/containers/sidebars/ProfileSidebar/index.tsx +++ b/src/containers/sidebars/ProfileSidebar/index.tsx @@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useMemo, VFC } from 'react'; import { CoverBackdrop } from '~/components/containers/CoverBackdrop'; import { ProfileSidebarNotes } from '~/components/profile/ProfileSidebarNotes'; +import { ProfileSidebarNotifications } from '~/components/profile/ProfileSidebarNotifications'; import { ProfileSidebarSettings } from '~/components/profile/ProfileSidebarSettings'; import { SidebarStack } from '~/components/sidebar/SidebarStack'; import { SidebarStackCard } from '~/components/sidebar/SidebarStackCard'; @@ -13,7 +14,7 @@ import { useUser } from '~/hooks/auth/useUser'; import type { SidebarComponentProps } from '~/types/sidebar'; import { isNil } from '~/utils/ramda'; -const tabs = ['profile', 'bookmarks'] as const; +const tabs = ['profile', 'notifications', 'bookmarks'] as const; type TabName = typeof tabs[number]; interface SettingsSidebarProps @@ -71,6 +72,7 @@ const SettingsSidebar: VFC = ({ + diff --git a/src/hooks/notifications/useNotificationSettings.ts b/src/hooks/notifications/useNotificationSettings.ts new file mode 100644 index 00000000..8e2f4f80 --- /dev/null +++ b/src/hooks/notifications/useNotificationSettings.ts @@ -0,0 +1,28 @@ +import { isAfter, isValid, parse, parseISO } from 'date-fns'; + +import { useAuth } from '../auth/useAuth'; + +import { useNotificationSettingsRequest } from './useNotificationSettingsRequest'; + +export const useNotificationSettings = () => { + const { isUser } = useAuth(); + + const { + error: settingsError, + enabled: settingsEnabled, + lastSeen, + lastDate, + isLoading: isLoadingSettings, + } = useNotificationSettingsRequest(); + + const enabled = !isLoadingSettings && !settingsError && settingsEnabled; + + const hasNew = + enabled && !!lastDate && (!lastSeen || isAfter(lastDate, lastSeen)); + + return { + enabled, + hasNew, + available: isUser, + }; +}; diff --git a/src/hooks/notifications/useNotificationSettingsRequest.ts b/src/hooks/notifications/useNotificationSettingsRequest.ts new file mode 100644 index 00000000..32e9de9a --- /dev/null +++ b/src/hooks/notifications/useNotificationSettingsRequest.ts @@ -0,0 +1,36 @@ +import { isValid, parseISO } from 'date-fns'; +import useSWR from 'swr'; + +import { apiGetNotificationSettings } from '~/api/notifications/settings'; +import { API } from '~/constants/api'; + +import { useAuth } from '../auth/useAuth'; + +const refreshInterval = 60e3; // 1min + +export const useNotificationSettingsRequest = () => { + const { isUser } = useAuth(); + const { + data, + isValidating: isLoading, + error, + } = useSWR( + isUser ? API.NOTIFICATIONS.SETTINGS : null, + async () => apiGetNotificationSettings(), + { refreshInterval }, + ); + + return { + isLoading, + error, + lastSeen: + data?.last_seen && isValid(parseISO(data.last_seen)) + ? parseISO(data?.last_seen) + : undefined, + lastDate: + data?.last_date && isValid(parseISO(data.last_date)) + ? parseISO(data?.last_date) + : undefined, + enabled: !!data?.enabled && (data.flow || data.comments), + }; +}; diff --git a/src/hooks/notifications/useNotificationsList.ts b/src/hooks/notifications/useNotificationsList.ts new file mode 100644 index 00000000..1f3cfd78 --- /dev/null +++ b/src/hooks/notifications/useNotificationsList.ts @@ -0,0 +1,20 @@ +import useSWR from 'swr'; + +import { apiGetNotifications } from '~/api/notifications/settings'; +import { API } from '~/constants/api'; + +import { useAuth } from '../auth/useAuth'; + +export const useNotificationsList = () => { + const { isUser } = useAuth(); + + const { + data, + isValidating: isLoading, + error, + } = useSWR(isUser ? API.NOTIFICATIONS.LIST : null, async () => + apiGetNotifications(), + ); + + return { isLoading, error, ...data }; +}; diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 6e8009c6..477d18f7 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,5 +1,3 @@ -import React from 'react'; - import App from 'next/app'; import Head from 'next/head'; @@ -16,14 +14,15 @@ import { UserContextProvider } from '~/utils/context/UserContextProvider'; import { AudioPlayerProvider } from '~/utils/providers/AudioPlayerProvider'; import { AuthProvider } from '~/utils/providers/AuthProvider'; import { MetadataProvider } from '~/utils/providers/MetadataProvider'; +import { NotificationProvider } from '~/utils/providers/NotificationProvider'; import { SWRConfigProvider } from '~/utils/providers/SWRConfigProvider'; import { SearchProvider } from '~/utils/providers/SearchProvider'; import { SidebarProvider } from '~/utils/providers/SidebarProvider'; import { ThemeProvider } from '~/utils/providers/ThemeProvider'; import { ToastProvider } from '~/utils/providers/ToastProvider'; -import '~/styles/main.scss'; import 'tippy.js/dist/tippy.css'; +import '~/styles/main.scss'; const mobxStore = getMOBXStore(); @@ -45,26 +44,28 @@ export default class MyApp extends App { - - - + + + + - {!!canonicalURL && ( - - )} - + {!!canonicalURL && ( + + )} + - - - - - - - - + + + + + + + + + diff --git a/src/styles/_mixins.scss b/src/styles/_mixins.scss index 905c471c..2a0f4a84 100644 --- a/src/styles/_mixins.scss +++ b/src/styles/_mixins.scss @@ -297,3 +297,11 @@ right: -10px; } } + +@mixin arrow_left($size: 50, $color: $content_bg) { + width: 0; + height: 0; + border-style: solid; + border-width: $size $size $size 0; + border-color: transparent $color transparent transparent; +} diff --git a/src/types/auth/index.ts b/src/types/auth/index.ts index 86779798..d34c6b3d 100644 --- a/src/types/auth/index.ts +++ b/src/types/auth/index.ts @@ -28,3 +28,9 @@ export interface ISocialAccount { name: string; photo: string; } + +export interface ShallowUser { + id: number; + username: string; + photo: string; +} diff --git a/src/types/notifications/index.ts b/src/types/notifications/index.ts new file mode 100644 index 00000000..0ceab48e --- /dev/null +++ b/src/types/notifications/index.ts @@ -0,0 +1,16 @@ +import { ShallowUser } from '../auth'; + +export interface NotificationItem { + id: number; + url: string; + type: NotificationType; + text: string; + user: ShallowUser; + thumbnail: string; + created_at: string; +} + +export enum NotificationType { + Node = 'node', + Comment = 'comment', +} diff --git a/src/utils/dom.ts b/src/utils/dom.ts index 1bed443a..91098e8c 100644 --- a/src/utils/dom.ts +++ b/src/utils/dom.ts @@ -88,10 +88,8 @@ export const getURL = ( return file?.url ? getURLFromString(file.url, size) : ''; }; -export const formatText = pipe( - formatTextSanitizeYoutube, +export const formatTextWithoutImages = pipe( formatTextComments, - formatTextTodos, formatExclamations, formatTextDash, formatTextMarkdown, @@ -99,6 +97,12 @@ export const formatText = pipe( formatTextClickableUsernames, ); +export const formatText = pipe( + formatTextSanitizeYoutube, + formatTextTodos, + formatTextWithoutImages, +); + export const formatTextParagraphs = (text: string): string => (text && formatText(text)) || ''; diff --git a/src/utils/providers/NotificationProvider.tsx b/src/utils/providers/NotificationProvider.tsx new file mode 100644 index 00000000..6f3d3c29 --- /dev/null +++ b/src/utils/providers/NotificationProvider.tsx @@ -0,0 +1,31 @@ +import { createContext, FC, useContext } from 'react'; + +import { observer } from 'mobx-react-lite'; + +import { useNotificationSettings } from '~/hooks/notifications/useNotificationSettings'; + +interface NotificationProviderProps {} + +const defaultValue = { + available: false, + enabled: false, + hasNew: false, +}; + +const NotificationContext = createContext(defaultValue); + +const NotificationProvider: FC = observer( + ({ children }) => { + const value = useNotificationSettings(); + + return ( + + {children} + + ); + }, +); + +export const useNotifications = () => useContext(NotificationContext); + +export { NotificationProvider };