diff --git a/src/components/main/Header/index.tsx b/src/components/main/Header/index.tsx index 42d5170e..f3a7fc14 100644 --- a/src/components/main/Header/index.tsx +++ b/src/components/main/Header/index.tsx @@ -1,11 +1,11 @@ -import React, { FC, useCallback, memo, useState, useEffect } from 'react'; +import React, { FC, useCallback, memo, useState, useEffect, useMemo } from 'react'; import { connect } from 'react-redux'; import { push as historyPush } from 'connected-react-router'; import { Link } from 'react-router-dom'; import { Logo } from '~/components/main/Logo'; import { Filler } from '~/components/containers/Filler'; -import { selectUser } from '~/redux/auth/selectors'; +import { selectUser, selectAuthUpdates } from '~/redux/auth/selectors'; import { Group } from '~/components/containers/Group'; import { DIALOGS } from '~/redux/modal/constants'; import pick from 'ramda/es/pick'; @@ -19,9 +19,12 @@ import classNames from 'classnames'; import * as style from './style.scss'; import * as MODAL_ACTIONS from '~/redux/modal/actions'; import * as AUTH_ACTIONS from '~/redux/auth/actions'; +import { IState } from '~/redux/store'; +import isBefore from 'date-fns/isBefore'; -const mapStateToProps = state => ({ - user: pick(['username', 'is_user', 'photo'])(selectUser(state)), +const mapStateToProps = (state: IState) => ({ + user: pick(['username', 'is_user', 'photo', 'last_seen_boris'])(selectUser(state)), + updates: pick(['boris_commented_at'])(selectAuthUpdates(state)), pathname: path(['router', 'location', 'pathname'], state), }); @@ -35,7 +38,15 @@ const mapDispatchToProps = { type IProps = ReturnType & typeof mapDispatchToProps & {}; const HeaderUnconnected: FC = memo( - ({ user, user: { is_user }, showDialog, pathname, authLogout, authOpenProfile }) => { + ({ + user, + user: { is_user, last_seen_boris }, + showDialog, + pathname, + updates: { boris_commented_at }, + authLogout, + authOpenProfile, + }) => { const [is_scrolled, setIsScrolled] = useState(false); const onLogin = useCallback(() => showDialog(DIALOGS.LOGIN), [showDialog]); @@ -55,6 +66,13 @@ const HeaderUnconnected: FC = memo( return () => window.removeEventListener('scroll', onScroll); }, [onScroll]); + const hasBorisUpdates = useMemo( + () => + boris_commented_at && + (!last_seen_boris || isBefore(new Date(last_seen_boris), new Date(boris_commented_at))), + [boris_commented_at, last_seen_boris] + ); + return createPortal(
@@ -71,7 +89,10 @@ const HeaderUnconnected: FC = memo( БОРИС diff --git a/src/components/main/Header/style.scss b/src/components/main/Header/style.scss index 4c0c8058..f180448f 100644 --- a/src/components/main/Header/style.scss +++ b/src/components/main/Header/style.scss @@ -90,6 +90,26 @@ transition: transform 0.5s, opacity 0.25s; } + &::after { + content: ' '; + position: absolute; + width: 8px; + height: 8px; + border-radius: 4px; + background: $red; + left: 50%; + bottom: -2px; + transform: translate(-50%, 0); + transition: opacity 0.5s; + opacity: 0; + } + + &.has_dot { + &::after { + opacity: 1; + } + } + @include tablet { padding: $gap; diff --git a/src/redux/auth/api.ts b/src/redux/auth/api.ts index a4e76dbb..f24dd08e 100644 --- a/src/redux/auth/api.ts +++ b/src/redux/auth/api.ts @@ -1,6 +1,6 @@ import { api, errorMiddleware, resultMiddleware, configWithToken } from '~/utils/api'; import { API } from '~/constants/api'; -import { IResultWithStatus, IMessage } from '~/redux/types'; +import { IResultWithStatus, IMessage, INotification } from '~/redux/types'; import { userLoginTransform } from '~/redux/auth/transforms'; import { IUser } from './types'; @@ -55,7 +55,9 @@ export const apiAuthGetUpdates = ({ access, exclude_dialogs, last, -}): Promise> => +}): Promise< + IResultWithStatus<{ notifications: INotification[]; boris: { commented_at: string } }> +> => api .get(API.USER.GET_UPDATES, configWithToken(access, { params: { exclude_dialogs, last } })) .then(resultMiddleware) diff --git a/src/redux/auth/reducer.ts b/src/redux/auth/reducer.ts index 679042de..9b05e13c 100644 --- a/src/redux/auth/reducer.ts +++ b/src/redux/auth/reducer.ts @@ -14,6 +14,7 @@ const INITIAL_STATE: IAuthState = { updates: { last: null, notifications: [], + boris_commented_at: null, }, login: { diff --git a/src/redux/auth/sagas.ts b/src/redux/auth/sagas.ts index 8b9e3a79..2f75616c 100644 --- a/src/redux/auth/sagas.ts +++ b/src/redux/auth/sagas.ts @@ -40,7 +40,7 @@ import { selectAuthUpdates, selectAuthRestore, } from './selectors'; -import { IResultWithStatus, INotification, IMessageNotification } from '../types'; +import { IResultWithStatus, INotification, IMessageNotification, Unwrap } from '../types'; import { IUser, IAuthState } from './types'; import { REHYDRATE, RehydrateAction } from 'redux-persist'; import { selectModal } from '~/redux/modal/selectors'; @@ -244,32 +244,42 @@ function* sendMessage({ message, onSuccess }: ReturnType } function* getUpdates() { - const user = yield select(selectAuthUser); + const user: ReturnType = yield select(selectAuthUser); if (!user || !user.is_user || user.role === USER_ROLES.GUEST || !user.id) return; const modal: IModalState = yield select(selectModal); const profile: IAuthState['profile'] = yield select(selectAuthProfile); - const { last }: IAuthState['updates'] = yield select(selectAuthUpdates); + const { last, boris_commented_at }: IAuthState['updates'] = yield select(selectAuthUpdates); const exclude_dialogs = modal.is_shown && modal.dialog === DIALOGS.PROFILE && profile.user.id ? profile.user.id : null; - const { error, data }: IResultWithStatus<{ notifications: INotification[] }> = yield call( + const { error, data }: Unwrap> = yield call( reqWrapper, apiAuthGetUpdates, { exclude_dialogs, last: last || user.last_seen_messages } ); - if (error || !data || !data.notifications || !data.notifications.length) return; + if (error || !data) { + return; + } - const { notifications } = data; + if (data.notifications && data.notifications.length) { + yield put( + authSetUpdates({ + last: data.notifications[0].created_at, + notifications: data.notifications, + }) + ); + } - yield put( - authSetUpdates({ - last: notifications[0].created_at, - notifications, - }) - ); + if (data.boris && data.boris.commented_at && boris_commented_at !== data.boris.commented_at) { + yield put( + authSetUpdates({ + boris_commented_at: data.boris.commented_at, + }) + ); + } } function* startPollingSaga() { diff --git a/src/redux/auth/types.ts b/src/redux/auth/types.ts index c44616cd..98f312f4 100644 --- a/src/redux/auth/types.ts +++ b/src/redux/auth/types.ts @@ -31,6 +31,7 @@ export type IAuthState = Readonly<{ updates: { last: string; notifications: INotification[]; + boris_commented_at: string; }; login: {