make notifications deleteable + unsub from the bell on notifications page

This commit is contained in:
Sean Yesmunt 2020-11-03 15:09:56 -05:00
parent a836467714
commit 485a734c9b
11 changed files with 140 additions and 9 deletions

View file

@ -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",

View file

@ -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);

View file

@ -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>
); );

View 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);

View 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>
);
}

View file

@ -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 }) });
} }
}} }}
/> />

View file

@ -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';

View file

@ -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,
}) })
); );

View file

@ -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.') }));
});
};
}

View file

@ -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) => {

View file

@ -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;
}
}
}