From ad85f71a4fc5def927d86c789d1fd5da20caeedf Mon Sep 17 00:00:00 2001 From: Fedor Katurov Date: Fri, 10 Mar 2023 17:30:10 +0600 Subject: [PATCH] added notification settings --- src/api/notifications/settings.ts | 17 ++++++++ src/api/notifications/types.ts | 13 ++++++ src/constants/api.ts | 4 ++ .../notifications/useNotificationSettings.ts | 28 ++++++++++++ .../useNotificationSettingsRequest.ts | 36 ++++++++++++++++ .../notifications/useNotificationsList.ts | 20 +++++++++ src/pages/_app.tsx | 43 ++++++++++--------- src/types/auth/index.ts | 6 +++ src/types/notifications/index.ts | 16 +++++++ src/utils/providers/NotificationProvider.tsx | 31 +++++++++++++ 10 files changed, 193 insertions(+), 21 deletions(-) create mode 100644 src/api/notifications/settings.ts create mode 100644 src/api/notifications/types.ts 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/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/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/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/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 };