Skip to content
This repository has been archived by the owner on Oct 11, 2022. It is now read-only.

Send notifications for watercooler threads #2964

Merged
merged 5 commits into from Apr 30, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 2 additions & 16 deletions api/mutations/message/addMessage.js
Expand Up @@ -8,10 +8,7 @@ import { storeMessage, getMessage } from '../../models/message';
import { setDirectMessageThreadLastActive } from '../../models/directMessageThread';
import { setUserLastSeenInDirectMessageThread } from '../../models/usersDirectMessageThreads';
import { createMemberInChannel } from '../../models/usersChannels';
import {
createParticipantInThread,
createParticipantWithoutNotificationsInThread,
} from '../../models/usersThreads';
import { createParticipantInThread } from '../../models/usersThreads';
import addCommunityMember from '../communityMember/addCommunityMember';
import { trackUserThreadLastSeenQueue } from 'shared/bull/queues';
import { toJSON } from 'shared/draft-utils';
Expand Down Expand Up @@ -179,17 +176,6 @@ export default async (
);
}

const participantPromise = async () => {
if (thread.watercooler) {
return await createParticipantWithoutNotificationsInThread(
message.threadId,
currentUser.id
);
} else {
return await createParticipantInThread(message.threadId, currentUser.id);
}
};

// dummy async function that will run if the user is already a member of the
// channel where the message is being sent
let membershipPromise = async () => await {};
Expand Down Expand Up @@ -220,7 +206,7 @@ export default async (
}

return membershipPromise()
.then(() => participantPromise())
.then(() => createParticipantInThread(message.threadId, currentUser.id))
.then(() => messagePromise())
.then(dbMessage => {
const contextPermissions = {
Expand Down
26 changes: 13 additions & 13 deletions src/views/thread/components/sidebar.js
Expand Up @@ -125,21 +125,21 @@ class Sidebar extends React.Component<Props> {
src={thread.community.profilePhoto}
/>
<SidebarCommunityName>{thread.community.name}</SidebarCommunityName>

<SidebarChannelPill>
<PillLink to={`/${thread.community.slug}/${thread.channel.slug}`}>
{thread.channel.isPrivate && (
<Lock>
<Icon glyph="private" size={12} />
</Lock>
)}
<PillLabel isPrivate={thread.channel.isPrivate}>
{thread.channel.name}
</PillLabel>
</PillLink>
</SidebarChannelPill>
</Link>

<SidebarChannelPill>
<PillLink to={`/${thread.community.slug}/${thread.channel.slug}`}>
{thread.channel.isPrivate && (
<Lock>
<Icon glyph="private" size={12} />
</Lock>
)}
<PillLabel isPrivate={thread.channel.isPrivate}>
{thread.channel.name}
</PillLabel>
</PillLink>
</SidebarChannelPill>

<SidebarCommunityDescription>
{renderDescriptionWithLinks(thread.community.description)}
</SidebarCommunityDescription>
Expand Down
179 changes: 179 additions & 0 deletions src/views/thread/components/watercoolerActionBar.js
@@ -0,0 +1,179 @@
// @flow
import * as React from 'react';
import { connect } from 'react-redux';
import Clipboard from 'react-clipboard.js';
import { addToastWithTimeout } from '../../../actions/toasts';
import { openModal } from '../../../actions/modals';
import Icon from '../../../components/icons';
import compose from 'recompose/compose';
import { track } from '../../../helpers/events';
import type { GetThreadType } from 'shared/graphql/queries/thread/getThread';
import toggleThreadNotificationsMutation from 'shared/graphql/mutations/thread/toggleThreadNotifications';
import {
FollowButton,
ShareButtons,
ShareButton,
WatercoolerActionBarContainer,
} from '../style';

type Props = {
thread: GetThreadType,
currentUser: Object,
dispatch: Function,
toggleThreadNotifications: Function,
};

type State = {
notificationStateLoading: boolean,
};

class WatercoolerActionBar extends React.Component<Props, State> {
state = { notificationStateLoading: false };

toggleNotification = () => {
const { thread, dispatch, toggleThreadNotifications } = this.props;
const threadId = thread.id;

this.setState({
notificationStateLoading: true,
});

toggleThreadNotifications({
threadId,
})
.then(({ data: { toggleThreadNotifications } }) => {
this.setState({
notificationStateLoading: false,
});

if (toggleThreadNotifications.receiveNotifications) {
track('thread', 'notifications turned on', null);
return dispatch(
addToastWithTimeout('success', 'Notifications activated!')
);
} else {
track('thread', 'notifications turned off', null);
return dispatch(
addToastWithTimeout('neutral', 'Notifications turned off')
);
}
})
.catch(err => {
this.setState({
notificationStateLoading: true,
});
dispatch(addToastWithTimeout('error', err.message));
});
};

render() {
const { thread, currentUser } = this.props;
const { notificationStateLoading } = this.state;

return (
<WatercoolerActionBarContainer>
<div style={{ display: 'flex' }}>
{currentUser ? (
<FollowButton
currentUser={currentUser}
icon={
thread.receiveNotifications
? 'notification-fill'
: 'notification'
}
tipText={
thread.receiveNotifications
? 'Turn off notifications'
: 'Get notified about replies'
}
tipLocation={'top-right'}
loading={notificationStateLoading}
onClick={this.toggleNotification}
dataCy="thread-notifications-toggle"
>
{thread.receiveNotifications ? 'Subscribed' : 'Get notifications'}
</FollowButton>
) : (
<FollowButton
currentUser={currentUser}
icon={'notification'}
tipText={'Get notified about replies'}
tipLocation={'top-right'}
dataCy="thread-notifications-login-capture"
onClick={() =>
this.props.dispatch(openModal('CHAT_INPUT_LOGIN_MODAL', {}))
}
>
Notify me
</FollowButton>
)}
{!thread.channel.isPrivate && (
<ShareButtons>
<ShareButton
facebook
tipText={'Share'}
tipLocation={'top-left'}
data-cy="thread-facebook-button"
>
<a
href={`https://www.facebook.com/sharer/sharer.php?u=https://spectrum.chat/thread/${
thread.id
}&t=${thread.content.title}`}
target="_blank"
rel="noopener noreferrer"
>
<Icon glyph={'facebook'} size={24} />
</a>
</ShareButton>

<ShareButton
twitter
tipText={'Tweet'}
tipLocation={'top-left'}
data-cy="thread-tweet-button"
>
<a
href={`https://twitter.com/share?text=${
thread.content.title
} on @withspectrum&url=https://spectrum.chat/thread/${
thread.id
}`}
target="_blank"
rel="noopener noreferrer"
>
<Icon glyph={'twitter'} size={24} />
</a>
</ShareButton>

<Clipboard
style={{ background: 'none' }}
data-clipboard-text={`https://spectrum.chat/thread/${
thread.id
}`}
onSuccess={() =>
this.props.dispatch(
addToastWithTimeout('success', 'Copied to clipboard')
)
}
>
<ShareButton
tipText={'Copy link'}
tipLocation={'top-left'}
data-cy="thread-copy-link-button"
>
<a>
<Icon glyph={'link'} size={24} />
</a>
</ShareButton>
</Clipboard>
</ShareButtons>
)}
</div>
</WatercoolerActionBarContainer>
);
}
}

export default compose(connect(), toggleThreadNotificationsMutation)(
WatercoolerActionBar
);
7 changes: 7 additions & 0 deletions src/views/thread/container.js
Expand Up @@ -36,6 +36,7 @@ import {
WatercoolerTitle,
WatercoolerAvatar,
} from './style';
import WatercoolerActionBar from './components/watercoolerActionBar';

type Props = {
data: {
Expand Down Expand Up @@ -412,6 +413,12 @@ class ThreadContainer extends React.Component<Props, State> {
Jump in to the conversation below or introduce yourself!
</WatercoolerDescription>
</WatercoolerIntroContainer>

<WatercoolerActionBar
thread={thread}
currentUser={currentUser}
/>

{!isEditing && (
<Messages
threadMessageCount={thread.messageCount}
Expand Down
5 changes: 4 additions & 1 deletion src/views/thread/style.js
Expand Up @@ -574,6 +574,10 @@ export const ActionBarContainer = styled.div`
}
`;

export const WatercoolerActionBarContainer = styled(ActionBarContainer)`
margin-bottom: 16px;
`;

export const FollowButton = styled(Button)`
background: ${props => props.theme.bg.default};
border: 1px solid ${props => props.theme.bg.border};
Expand Down Expand Up @@ -741,7 +745,6 @@ export const WatercoolerIntroContainer = styled.div`
align-items: center;
justify-content: center;
padding: 32px 32px 36px;
border-bottom: 1px solid ${props => props.theme.bg.border};
background: ${props => props.theme.bg.default};
flex: auto;
flex-direction: column;
Expand Down