make notifications deleteable + unsub from the bell on notifications page
This commit is contained in:
parent
a836467714
commit
485a734c9b
11 changed files with 140 additions and 9 deletions
|
@ -1464,7 +1464,7 @@
|
||||||
"Your other content language": "Your other content language",
|
"Your other content language": "Your other content language",
|
||||||
"Search only in this language by default": "Search only in this language by default",
|
"Search only in this language by default": "Search only in this language by default",
|
||||||
"This link leads to an external website.": "This link leads to an external website.",
|
"This link leads to an external website.": "This link leads to an external website.",
|
||||||
"Hold on, we are setting up your account": "Hold on, we are setting up your account",
|
"Please wait a bit, we are still getting your account ready. on, we are setting up your account": "Hold on, we are setting up your account",
|
||||||
"No Content Found": "No Content Found",
|
"No Content Found": "No Content Found",
|
||||||
"Publish Something": "Publish Something",
|
"Publish Something": "Publish Something",
|
||||||
"Watch on lbry.tv": "Watch on lbry.tv",
|
"Watch on lbry.tv": "Watch on lbry.tv",
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { doSeeNotifications } from 'redux/actions/notifications';
|
import { doSeeNotifications, doDeleteNotification } from 'redux/actions/notifications';
|
||||||
import Notification from './view';
|
import Notification from './view';
|
||||||
|
|
||||||
export default connect(null, {
|
export default connect(null, {
|
||||||
doSeeNotifications,
|
doSeeNotifications,
|
||||||
|
doDeleteNotification,
|
||||||
})(Notification);
|
})(Notification);
|
||||||
|
|
|
@ -9,27 +9,31 @@ import Icon from 'component/common/icon';
|
||||||
import DateTime from 'component/dateTime';
|
import DateTime from 'component/dateTime';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import ChannelThumbnail from 'component/channelThumbnail';
|
import ChannelThumbnail from 'component/channelThumbnail';
|
||||||
import { MenuItem } from '@reach/menu-button';
|
|
||||||
import { formatLbryUrlForWeb } from 'util/url';
|
import { formatLbryUrlForWeb } from 'util/url';
|
||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
import { parseURI } from 'lbry-redux';
|
import { parseURI } from 'lbry-redux';
|
||||||
import { PAGE_VIEW_QUERY, DISCUSSION_PAGE } from 'page/channel/view';
|
import { PAGE_VIEW_QUERY, DISCUSSION_PAGE } from 'page/channel/view';
|
||||||
import FileThumbnail from 'component/fileThumbnail';
|
import FileThumbnail from 'component/fileThumbnail';
|
||||||
|
import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
|
||||||
|
import NotificationContentChannelMenu from 'component/notificationContentChannelMenu';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
notification: WebNotification,
|
notification: WebNotification,
|
||||||
menuButton: boolean,
|
menuButton: boolean,
|
||||||
children: any,
|
children: any,
|
||||||
doSeeNotifications: ([number]) => void,
|
doSeeNotifications: ([number]) => void,
|
||||||
|
doDeleteNotification: number => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Notification(props: Props) {
|
export default function Notification(props: Props) {
|
||||||
const { notification, menuButton = false, doSeeNotifications } = props;
|
const { notification, menuButton = false, doSeeNotifications, doDeleteNotification } = props;
|
||||||
const { push } = useHistory();
|
const { push } = useHistory();
|
||||||
const { notification_rule, notification_parameters, is_seen, id } = notification;
|
const { notification_rule, notification_parameters, is_seen, id } = notification;
|
||||||
const isCommentNotification =
|
const isCommentNotification =
|
||||||
notification_rule === NOTIFICATIONS.NOTIFICATION_COMMENT || notification_rule === NOTIFICATIONS.NOTIFICATION_REPLY;
|
notification_rule === NOTIFICATIONS.NOTIFICATION_COMMENT || notification_rule === NOTIFICATIONS.NOTIFICATION_REPLY;
|
||||||
const commentText = isCommentNotification && notification_parameters.dynamic.comment;
|
const commentText = isCommentNotification && notification_parameters.dynamic.comment;
|
||||||
|
const channelUrl =
|
||||||
|
(notification_rule === NOTIFICATIONS.NEW_CONTENT && notification.notification_parameters.dynamic.channel_url) || '';
|
||||||
|
|
||||||
let notificationTarget;
|
let notificationTarget;
|
||||||
switch (notification_rule) {
|
switch (notification_rule) {
|
||||||
|
@ -153,12 +157,29 @@ export default function Notification(props: Props) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="notification__extra">
|
<div className="notification__extra">
|
||||||
|
{!is_seen && <Button className="notification__mark-seen" onClick={handleSeeNotification} />}
|
||||||
<div className="notification__time">
|
<div className="notification__time">
|
||||||
<DateTime timeAgo date={notification.active_at} />
|
<DateTime timeAgo date={notification.active_at} />
|
||||||
</div>
|
</div>
|
||||||
{!is_seen && <Button className="notification__mark-seen" onClick={handleSeeNotification} />}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="notification__menu">
|
||||||
|
<Menu>
|
||||||
|
<MenuButton onClick={e => e.stopPropagation()}>
|
||||||
|
<Icon size={18} icon={ICONS.MORE_VERTICAL} />
|
||||||
|
</MenuButton>
|
||||||
|
<MenuList className="menu__list--comments">
|
||||||
|
<MenuItem className="menu__link" onSelect={() => doDeleteNotification(id)}>
|
||||||
|
<Icon aria-hidden icon={ICONS.DELETE} />
|
||||||
|
{__('Delete')}
|
||||||
|
</MenuItem>
|
||||||
|
{notification_rule === NOTIFICATIONS.NEW_CONTENT && channelUrl ? (
|
||||||
|
<NotificationContentChannelMenu uri={channelUrl} />
|
||||||
|
) : null}
|
||||||
|
</MenuList>
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
|
|
14
ui/component/notificationContentChannelMenu/index.js
Normal file
14
ui/component/notificationContentChannelMenu/index.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { doChannelSubscribe } from 'redux/actions/subscriptions';
|
||||||
|
import { doToast } from 'redux/actions/notifications';
|
||||||
|
import { makeSelectNotificationsDisabled } from 'redux/selectors/subscriptions';
|
||||||
|
import SubscribeButton from './view';
|
||||||
|
|
||||||
|
const select = (state, props) => ({
|
||||||
|
notificationsDisabled: makeSelectNotificationsDisabled(props.uri)(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, {
|
||||||
|
doChannelSubscribe,
|
||||||
|
doToast,
|
||||||
|
})(SubscribeButton);
|
42
ui/component/notificationContentChannelMenu/view.jsx
Normal file
42
ui/component/notificationContentChannelMenu/view.jsx
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
// @flow
|
||||||
|
import * as ICONS from 'constants/icons';
|
||||||
|
import React from 'react';
|
||||||
|
import { MenuItem } from '@reach/menu-button';
|
||||||
|
import { parseURI } from 'lbry-redux';
|
||||||
|
import Icon from 'component/common/icon';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
uri: string,
|
||||||
|
notificationsDisabled: boolean,
|
||||||
|
doToast: ({ message: string }) => void,
|
||||||
|
doChannelSubscribe: Subscription => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function NotificationContentChannelMenu(props: Props) {
|
||||||
|
const { uri, notificationsDisabled, doToast, doChannelSubscribe } = props;
|
||||||
|
const { claimName } = parseURI(uri);
|
||||||
|
|
||||||
|
function handleClick() {
|
||||||
|
doChannelSubscribe({
|
||||||
|
uri,
|
||||||
|
channelName: claimName,
|
||||||
|
notificationsDisabled: !notificationsDisabled,
|
||||||
|
});
|
||||||
|
|
||||||
|
doToast({
|
||||||
|
message: !notificationsDisabled
|
||||||
|
? __('Notifications turned off for %channel%.', { channel: claimName })
|
||||||
|
: __('Notifications turned on for %channel%.', { channel: claimName }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuItem onSelect={handleClick}>
|
||||||
|
<div className="menu__link">
|
||||||
|
<Icon aria-hidden icon={notificationsDisabled ? ICONS.BELL : ICONS.BELL_ON} />
|
||||||
|
{notificationsDisabled ? __('Turn Back On') : __('Turn Off')}
|
||||||
|
</div>
|
||||||
|
<span className="menu__link-help">{claimName}</span>
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
}
|
|
@ -94,7 +94,7 @@ export default function SubscribeButton(props: Props) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (newNotificationsDisabled === false) {
|
if (newNotificationsDisabled === false) {
|
||||||
doToast({ message: __('Notifications enabled for %channel%!', { channel: claimName }) });
|
doToast({ message: __('Notifications turned on for %channel%!', { channel: claimName }) });
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -238,6 +238,9 @@ export const NOTIFICATION_READ_FAILED = 'NOTIFICATION_READ_FAILED';
|
||||||
export const NOTIFICATION_SEEN_STARTED = 'NOTIFICATION_SEEN_STARTED';
|
export const NOTIFICATION_SEEN_STARTED = 'NOTIFICATION_SEEN_STARTED';
|
||||||
export const NOTIFICATION_SEEN_COMPLETED = 'NOTIFICATION_SEEN_COMPLETED';
|
export const NOTIFICATION_SEEN_COMPLETED = 'NOTIFICATION_SEEN_COMPLETED';
|
||||||
export const NOTIFICATION_SEEN_FAILED = 'NOTIFICATION_SEEN_FAILED';
|
export const NOTIFICATION_SEEN_FAILED = 'NOTIFICATION_SEEN_FAILED';
|
||||||
|
export const NOTIFICATION_DELETE_STARTED = 'NOTIFICATION_DELETE_STARTED';
|
||||||
|
export const NOTIFICATION_DELETE_COMPLETED = 'NOTIFICATION_DELETE_COMPLETED';
|
||||||
|
export const NOTIFICATION_DELETE_FAILED = 'NOTIFICATION_DELETE_FAILED';
|
||||||
export const CREATE_TOAST = 'CREATE_TOAST';
|
export const CREATE_TOAST = 'CREATE_TOAST';
|
||||||
export const DISMISS_TOAST = 'DISMISS_TOAST';
|
export const DISMISS_TOAST = 'DISMISS_TOAST';
|
||||||
export const CREATE_ERROR = 'CREATE_ERROR';
|
export const CREATE_ERROR = 'CREATE_ERROR';
|
||||||
|
|
|
@ -338,7 +338,7 @@ export function doAlertWaitingForSync() {
|
||||||
return dispatch =>
|
return dispatch =>
|
||||||
dispatch(
|
dispatch(
|
||||||
doToast({
|
doToast({
|
||||||
message: __('Hold on, we are setting up your account'),
|
message: __('Please wait a bit, we are still getting your account ready.'),
|
||||||
isError: false,
|
isError: false,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
|
@ -126,3 +126,15 @@ export function doSeeAllNotifications() {
|
||||||
dispatch(doSeeNotifications(unSeenNotifications));
|
dispatch(doSeeNotifications(unSeenNotifications));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function doDeleteNotification(notificationId: number) {
|
||||||
|
return (dispatch: Dispatch<*>) => {
|
||||||
|
Lbryio.call('notification', 'delete', { notification_ids: notificationId })
|
||||||
|
.then(() => {
|
||||||
|
dispatch({ type: ACTIONS.NOTIFICATION_DELETE_COMPLETED, data: { notificationId } });
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
dispatch(doToast({ isError: true, message: __('Unable to delete this right now. Please try again later.') }));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -82,6 +82,18 @@ export default handleActions(
|
||||||
notifications: newNotifications,
|
notifications: newNotifications,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
[ACTIONS.NOTIFICATION_DELETE_COMPLETED]: (state, action) => {
|
||||||
|
const { notifications } = state;
|
||||||
|
const { notificationId } = action.data;
|
||||||
|
const newNotifications = notifications.filter(notification => {
|
||||||
|
return notification.id !== notificationId;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
notifications: newNotifications,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
// Errors
|
// Errors
|
||||||
[ACTIONS.CREATE_ERROR]: (state: NotificationState, action: DoError) => {
|
[ACTIONS.CREATE_ERROR]: (state: NotificationState, action: DoError) => {
|
||||||
|
|
|
@ -34,13 +34,14 @@ $contentMaxWidth: 35rem;
|
||||||
|
|
||||||
@media (min-width: $breakpoint-small) {
|
@media (min-width: $breakpoint-small) {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
margin-left: var(--spacing-m);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification__wrapper {
|
.notification__wrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: var(--spacing-m);
|
padding: var(--spacing-m) 0;
|
||||||
|
|
||||||
.channel-thumbnail {
|
.channel-thumbnail {
|
||||||
@include handleChannelGif(3rem);
|
@include handleChannelGif(3rem);
|
||||||
|
@ -111,7 +112,7 @@ $contentMaxWidth: 35rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification__text-wrapper {
|
.notification__text-wrapper {
|
||||||
max-width: calc(#{$contentMaxWidth} - (#{$thumbnailWidth} * 16 / 9) - var(--spacing-m)); //25rem;
|
max-width: calc(#{$contentMaxWidth} - (#{$thumbnailWidth} * 16 / 9) - var(--spacing-m));
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification__title {
|
.notification__title {
|
||||||
|
@ -179,6 +180,15 @@ $contentMaxWidth: 35rem;
|
||||||
.notification__extra {
|
.notification__extra {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-right: var(--spacing-m);
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
|
||||||
|
@media (min-width: $breakpoint-small) {
|
||||||
|
margin-left: var(--spacing-s);
|
||||||
|
margin-top: 0;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification__mark-seen {
|
.notification__mark-seen {
|
||||||
|
@ -193,3 +203,19 @@ $contentMaxWidth: 35rem;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.notification__menu {
|
||||||
|
margin-right: var(--spacing-m);
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
stroke: var(--color-text-help);
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
@include linkFocus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue