mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 21:06:42 +07:00
message notifications working
This commit is contained in:
parent
83c9900af1
commit
7583a57b04
11 changed files with 137 additions and 32 deletions
|
@ -7,6 +7,7 @@ import pick from 'ramda/es/pick';
|
|||
import classNames from 'classnames';
|
||||
import * as AUTH_ACTIONS from '~/redux/auth/actions';
|
||||
import { NotificationBubble } from '../../notifications/NotificationBubble';
|
||||
import { INotification, IMessageNotification } from '~/redux/types';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
user: pick(['last_seen_messages'], selectAuthUser(state)),
|
||||
|
@ -15,6 +16,7 @@ const mapStateToProps = state => ({
|
|||
|
||||
const mapDispatchToProps = {
|
||||
authSetLastSeenMessages: AUTH_ACTIONS.authSetLastSeenMessages,
|
||||
authOpenProfile: AUTH_ACTIONS.authOpenProfile,
|
||||
};
|
||||
|
||||
type IProps = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
|
||||
|
@ -23,8 +25,9 @@ const NotificationsUnconnected: FC<IProps> = ({
|
|||
updates: { last, notifications },
|
||||
user: { last_seen_messages },
|
||||
authSetLastSeenMessages,
|
||||
authOpenProfile,
|
||||
}) => {
|
||||
const [visible, setVisible] = useState(true);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const has_new = useMemo(
|
||||
() =>
|
||||
notifications.length &&
|
||||
|
@ -35,21 +38,42 @@ const NotificationsUnconnected: FC<IProps> = ({
|
|||
[last, last_seen_messages, notifications]
|
||||
);
|
||||
|
||||
const onNotificationClick = useCallback(
|
||||
(notification: INotification) => {
|
||||
switch (notification.type) {
|
||||
case 'message':
|
||||
return authOpenProfile(
|
||||
(notification as IMessageNotification).content.from.username,
|
||||
'messages'
|
||||
);
|
||||
default:
|
||||
return;
|
||||
}
|
||||
},
|
||||
[authOpenProfile]
|
||||
);
|
||||
const showList = useCallback(() => setVisible(true), [setVisible]);
|
||||
const hideList = useCallback(() => setVisible(false), [setVisible]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!visible || !has_new) return;
|
||||
authSetLastSeenMessages(new Date().toISOString());
|
||||
}, [visible]);
|
||||
|
||||
const showList = useCallback(() => setVisible(true), [setVisible]);
|
||||
const hideList = useCallback(() => setVisible(false), [setVisible]);
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.wrap, { [styles.is_new]: has_new })}>
|
||||
<div
|
||||
className={classNames(styles.wrap, {
|
||||
[styles.is_new]: has_new,
|
||||
[styles.active]: notifications.length > 0,
|
||||
})}
|
||||
>
|
||||
<div className={styles.icon} onFocus={showList} onBlur={hideList} tabIndex={-1}>
|
||||
{has_new ? <Icon icon="bell_ring" size={24} /> : <Icon icon="bell" size={24} />}
|
||||
</div>
|
||||
|
||||
{visible && <NotificationBubble notifications={notifications} />}
|
||||
{visible && (
|
||||
<NotificationBubble notifications={notifications} onClick={onNotificationClick} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -21,9 +21,16 @@
|
|||
position: relative;
|
||||
outline: none;
|
||||
|
||||
&.active {
|
||||
.icon {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.is_new {
|
||||
.icon {
|
||||
animation: ring 1s infinite alternate;
|
||||
opacity: 1;
|
||||
|
||||
svg {
|
||||
fill: $red;
|
||||
|
@ -35,4 +42,5 @@
|
|||
.icon {
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
|
|
@ -2,28 +2,37 @@ import React, { FC, createElement } from 'react';
|
|||
import { INotification, NOTIFICATION_TYPES } from '~/redux/types';
|
||||
import styles from './styles.scss';
|
||||
import { NotificationMessage } from '../NotificationMessage';
|
||||
import { Icon } from '~/components/input/Icon';
|
||||
|
||||
interface IProps {
|
||||
notifications: INotification[];
|
||||
onClick: (notification: INotification) => void;
|
||||
}
|
||||
|
||||
const NOTIFICATION_RENDERERS = {
|
||||
[NOTIFICATION_TYPES.message]: NotificationMessage,
|
||||
};
|
||||
|
||||
const NotificationBubble: FC<IProps> = ({ notifications }) => {
|
||||
const NotificationBubble: FC<IProps> = ({ notifications, onClick }) => {
|
||||
return (
|
||||
<div className={styles.wrap}>
|
||||
<div className={styles.list}>
|
||||
{notifications
|
||||
.filter(notification => notification.type && NOTIFICATION_RENDERERS[notification.type])
|
||||
.map(notification =>
|
||||
createElement(NOTIFICATION_RENDERERS[notification.type], {
|
||||
notification,
|
||||
onClick: console.log,
|
||||
key: notification.content.id,
|
||||
})
|
||||
)}
|
||||
{notifications.length === 0 && (
|
||||
<div className={styles.placeholder}>
|
||||
<Icon icon="bell_ring" />
|
||||
<div>НЕТ УВЕДОМЛЕНИЙ</div>
|
||||
</div>
|
||||
)}
|
||||
{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,
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
$notification_color: darken($content_bg, 2%);
|
||||
|
||||
@keyframes appear {
|
||||
0% {
|
||||
opacity: 0;
|
||||
|
@ -6,15 +8,16 @@
|
|||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.wrap {
|
||||
position: absolute;
|
||||
position: absolute;
|
||||
background: $content_bg;
|
||||
top: 50px;
|
||||
background: $notification_color;
|
||||
top: 42px;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
border-radius: $radius;
|
||||
animation: appear 0.5s forwards;
|
||||
animation: appear 0.25s forwards;
|
||||
z-index: 2;
|
||||
|
||||
&::before {
|
||||
content: ' ';
|
||||
|
@ -22,7 +25,7 @@
|
|||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 0 0 16px 16px;
|
||||
border-color: transparent transparent $content_bg transparent;
|
||||
border-color: transparent transparent $notification_color transparent;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: -16px;
|
||||
|
@ -45,6 +48,7 @@
|
|||
flex-direction: column;
|
||||
padding: $gap;
|
||||
min-width: 0;
|
||||
cursor: pointer;
|
||||
|
||||
svg {
|
||||
fill: white;
|
||||
|
@ -74,3 +78,23 @@
|
|||
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;
|
||||
|
||||
svg {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
opacity: 0.05;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue