[deploy] 🚀 Feature: Chat Channel Request manager for mods (#7904)
* Feature 🚀 : Ability to delete messages in chat channels - Sending message ID to frontend - Deleting Message - Use pusher to delete message realtime * Minor Bug 🐞: Show message action only for current user - User can delete or edit their own messages * Test cases added * Bug 🐞: Update message id for receiver Message id was not sent to receiver by pusher * Refactoring🛠: Message controller refactoring * Test Cases📝 : Specs for Delete message added * Feature 🚀 : Ability to edit messages * Test Cases📝 : Specs for Edit message added * 🐞 Channel List bug in mobile view * changes in joining request responses * 🛠 Request Managers Frontend ready * changes in routes for adding and removing remembership * 🚀 New routes implemented for request manager * fix issues * changes in api for remove membership * Merge conflict resolved * 🐞 Channel List bug in mobile view * changes in joining request responses * 🛠 Request Managers Frontend ready * changes in routes for adding and removing remembership * 🚀 New routes implemented for request manager * fix issues * changes in api for remove membership * 🛠Optimizing for CodeClimate * 🛠Optimizing again for CodeClimate * 🛠Optimizing again 2 for CodeClimate * 🛠 Added more test cases * 🛠 Optimizing code * 🛠 vscode file changed * 🛠 Updated schema as requested Co-authored-by: Parasgr-code <paras.gaur@skynox.tech>
This commit is contained in:
parent
dc48886b7d
commit
40dc8d1349
16 changed files with 476 additions and 110 deletions
|
|
@ -531,7 +531,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
.chat__channelinvitationsindicator a {
|
||||
.chat__channelinvitationsindicator a,
|
||||
.chat__channelinvitationsindicator button {
|
||||
background: linear-gradient(10deg, darken($green, 25%), darken($green, 15%));
|
||||
text-align: center;
|
||||
padding: 30px 0px;
|
||||
|
|
@ -1265,3 +1266,11 @@
|
|||
border-radius: '500px';
|
||||
background-color: '#f5f6f7';
|
||||
}
|
||||
.request-card {
|
||||
display: grid;
|
||||
grid-template-columns: 200px 1fr;
|
||||
}
|
||||
|
||||
.action button:nth-child(2n) {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ class ChatChannelMembershipsController < ApplicationController
|
|||
@chat_channel = ChatChannel.find(params[:chat_channel_id])
|
||||
authorize @chat_channel, :update?
|
||||
@chat_channel_membership = @chat_channel.chat_channel_memberships.find(params[:membership_id])
|
||||
membership = ChatChannelMembership.find_by!(chat_channel_id: params[:chat_channel_id], user: current_user)
|
||||
if params[:status] == "pending"
|
||||
@chat_channel_membership.destroy
|
||||
flash[:settings_notice] = "Invitation removed."
|
||||
|
|
@ -67,8 +68,11 @@ class ChatChannelMembershipsController < ApplicationController
|
|||
send_chat_action_message("@#{current_user.username} removed @#{@chat_channel_membership.user.username} from #{@chat_channel_membership.channel_name}", current_user, @chat_channel_membership.chat_channel_id, "removed_from_channel")
|
||||
@chat_channel_membership.update(status: "removed_from_channel")
|
||||
flash[:settings_notice] = "Removed #{@chat_channel_membership.user.name}"
|
||||
respond_to do |format|
|
||||
format.html { redirect_to edit_chat_channel_membership_path(membership) }
|
||||
format.json { render json: { status: "success", message: "Membership removed" } }
|
||||
end && return
|
||||
end
|
||||
membership = ChatChannelMembership.find_by!(chat_channel_id: params[:chat_channel_id], user: current_user)
|
||||
redirect_to edit_chat_channel_membership_path(membership)
|
||||
end
|
||||
|
||||
|
|
@ -120,7 +124,10 @@ class ChatChannelMembershipsController < ApplicationController
|
|||
NotifyMailer.channel_invite_email(@chat_channel_membership, @chat_channel_membership.user).deliver_later
|
||||
flash[:settings_notice] = "Accepted request of #{@chat_channel_membership.user.username} to join #{channel_name}."
|
||||
membership = ChatChannelMembership.find_by!(chat_channel_id: @chat_channel_membership.chat_channel.id, user: current_user)
|
||||
redirect_to(edit_chat_channel_membership_path(membership)) && return
|
||||
respond_to do |format|
|
||||
format.html { redirect_to(edit_chat_channel_membership_path(membership)) }
|
||||
format.json { render json: { status: "success", message: "Accepted Request" } }
|
||||
end && return
|
||||
end
|
||||
else
|
||||
@chat_channel_membership.update(status: "rejected")
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ class ChatChannelsController < ApplicationController
|
|||
|
||||
def render_unopened_json_response
|
||||
@chat_channels_memberships = if session_current_user_id
|
||||
ChatChannelMembership.where(user_id: session_current_user_id).includes(:chat_channel).
|
||||
ChatChannelMembership.where(user_id: session_current_user_id).includes(%i[chat_channel user]).
|
||||
where(has_unopened_messages: true).
|
||||
where(show_global_badge_notification: true).
|
||||
where.not(status: %w[removed_from_channel left_channel]).
|
||||
|
|
@ -177,9 +177,11 @@ class ChatChannelsController < ApplicationController
|
|||
end
|
||||
|
||||
def render_joining_request_json_response
|
||||
requested_memberships = current_user.chat_channel_memberships.includes(:chat_channel).
|
||||
where(chat_channels: { discoverable: true }, role: "mod").pluck(:chat_channel_id).map { |membership_id| ChatChannel.find_by(id: membership_id).requested_memberships }
|
||||
render json: { joining_requests: requested_memberships.flatten }
|
||||
requested_memberships_id = current_user.chat_channel_memberships.includes(:chat_channel).
|
||||
where(chat_channels: { discoverable: true }, role: "mod").pluck(:chat_channel_id).map { |membership_id| ChatChannel.find_by(id: membership_id).requested_memberships }.flatten.map(&:id)
|
||||
@chat_channels_memberships = ChatChannelMembership.includes(%i[user chat_channel]).where(id: requested_memberships_id)
|
||||
|
||||
render "index.json"
|
||||
end
|
||||
|
||||
def render_channels_html
|
||||
|
|
|
|||
|
|
@ -1,10 +1,67 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<Content /> as loading-user should render and test snapshot 1`] = `
|
||||
exports[`<Content /> as channel-request should render and test snapshot 1`] = `
|
||||
<div
|
||||
class="activechatchannel__activecontent activechatchannel__activecontent--sidecar"
|
||||
id="chat_activecontent"
|
||||
>
|
||||
<button
|
||||
class="activechatchannel__activecontentexitbutton crayons-btn crayons-btn--secondary"
|
||||
data-content="exit"
|
||||
>
|
||||
<svg
|
||||
data-content="exit"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M0 0h24v24H0z"
|
||||
data-content="exit"
|
||||
fill="none"
|
||||
/>
|
||||
<path
|
||||
d="M12 10.586l4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636z"
|
||||
data-content="exit"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="activechatchannel__activecontentexitbutton activechatchannel__activecontentexitbutton--fullscreen crayons-btn crayons-btn--secondary"
|
||||
data-content="fullscreen"
|
||||
style={
|
||||
Object {
|
||||
"left": "39px",
|
||||
}
|
||||
}
|
||||
>
|
||||
|
||||
<svg
|
||||
data-content="fullscreen"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M0 0h24v24H0z"
|
||||
data-content="fullscreen"
|
||||
fill="none"
|
||||
/>
|
||||
<path
|
||||
d="M20 3h2v6h-2V5h-4V3h4zM4 3h4v2H4v4H2V3h2zm16 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z"
|
||||
data-content="fullscreen"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<Content /> as loading-user should render and test snapshot 1`] = `
|
||||
<div
|
||||
class="activechatchannel__activecontent activechatchannel__activecontent--sidecar"
|
||||
id="chat_activecontent"
|
||||
onClick={false}
|
||||
>
|
||||
<button
|
||||
class="activechatchannel__activecontentexitbutton crayons-btn crayons-btn--secondary"
|
||||
|
|
@ -56,8 +113,5 @@ exports[`<Content /> as loading-user should render and test snapshot 1`] = `
|
|||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<div
|
||||
class="loading-user"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<RequestManager /> should render and test snapshot 1`] = `
|
||||
<div
|
||||
class="activechatchannel__activeArticle activesendrequest"
|
||||
>
|
||||
<div
|
||||
class="request_manager_header crayons-card mb-6 grid grid-flow-row gap-6 p-6"
|
||||
>
|
||||
<h1>
|
||||
Joining Request
|
||||
</h1>
|
||||
<h3>
|
||||
Manage request comming to all the channels
|
||||
</h3>
|
||||
<div
|
||||
class="crayons-field"
|
||||
>
|
||||
<h1>
|
||||
ironman
|
||||
</h1>
|
||||
<div
|
||||
class="request-card"
|
||||
>
|
||||
<p />
|
||||
<div
|
||||
class="action"
|
||||
>
|
||||
<button
|
||||
class="crayons-btn crayons-btn--s crayons-btn--danger"
|
||||
data-channel-id={2}
|
||||
type="button"
|
||||
>
|
||||
Reject
|
||||
</button>
|
||||
<button
|
||||
class="crayons-btn crayons-btn--s"
|
||||
data-channel-id={2}
|
||||
type="button"
|
||||
>
|
||||
Accept
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -3,24 +3,45 @@ import render from 'preact-render-to-json';
|
|||
import { shallow } from 'preact-render-spy';
|
||||
import Content from '../content';
|
||||
|
||||
const getContent = () => (
|
||||
<Content
|
||||
onTriggerContent={false}
|
||||
resource={{ type_of: 'loading-user' }}
|
||||
activeChannelId={12345}
|
||||
pusherKey="ASDFGHJKL"
|
||||
githubToken=""
|
||||
/>
|
||||
);
|
||||
const data = [
|
||||
{
|
||||
onTriggerContent: false,
|
||||
resource: { type_of: 'channel-request' },
|
||||
activeChannelId: 12345,
|
||||
pusherKey: 'ASDFGHJKL',
|
||||
githubToken: '',
|
||||
},
|
||||
{
|
||||
onTriggerContent: false,
|
||||
resource: { type_of: 'loading-user' },
|
||||
activeChannelId: 1235,
|
||||
pusherKey: 'ASDFGHJKL',
|
||||
githubToken: '',
|
||||
},
|
||||
];
|
||||
|
||||
const getContent = (resource) => <Content resource={resource} />;
|
||||
|
||||
describe('<Content />', () => {
|
||||
describe('as loading-user', () => {
|
||||
it('should render and test snapshot', () => {
|
||||
const tree = render(getContent());
|
||||
const tree = render(getContent(data[0]));
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
it('should have proper elements, attributes and content', () => {
|
||||
const context = shallow(getContent());
|
||||
const context = shallow(getContent(data[0]));
|
||||
expect(
|
||||
context.find('.activechatchannel__activecontent').exists(),
|
||||
).toEqual(true);
|
||||
});
|
||||
});
|
||||
describe('as channel-request', () => {
|
||||
it('should render and test snapshot', () => {
|
||||
const tree = render(getContent(data[1]));
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
it('should have proper elements, attributes and content', () => {
|
||||
const context = shallow(getContent(data[1]));
|
||||
expect(
|
||||
context.find('.activechatchannel__activecontent').exists(),
|
||||
).toEqual(true);
|
||||
|
|
|
|||
26
app/javascript/chat/__tests__/requestManager.test.jsx
Normal file
26
app/javascript/chat/__tests__/requestManager.test.jsx
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { h } from 'preact';
|
||||
import render from 'preact-render-to-json';
|
||||
import { shallow } from 'preact-render-spy';
|
||||
import RequestManager from '../requestManager';
|
||||
|
||||
const data = [
|
||||
{
|
||||
id: 2,
|
||||
channel_name: 'ironman',
|
||||
},
|
||||
];
|
||||
|
||||
const getRequestManager = (resource) => <RequestManager resource={resource} />;
|
||||
|
||||
describe('<RequestManager />', () => {
|
||||
it('should render and test snapshot', () => {
|
||||
const tree = render(getRequestManager(data));
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should have the proper elements, attributes and values', () => {
|
||||
const context = shallow(getRequestManager(data));
|
||||
|
||||
expect(context.find('.request_manager_header').exists()).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { fetchSearch } from '../utilities/search';
|
||||
import { createDataHash } from '../util';
|
||||
|
||||
export function getAllMessages(channelId, messageOffset, successCb, failureCb) {
|
||||
fetch(`/chat_channels/${channelId}?message_offset=${messageOffset}`, {
|
||||
|
|
@ -98,37 +98,22 @@ export function conductModeration(
|
|||
}
|
||||
|
||||
export function getChannels(
|
||||
query,
|
||||
retrievalID,
|
||||
searchType,
|
||||
paginationNumber,
|
||||
searchParams,
|
||||
additionalFilters,
|
||||
successCb,
|
||||
_failureCb,
|
||||
) {
|
||||
const dataHash = {};
|
||||
if (additionalFilters.filters) {
|
||||
const [key, value] = additionalFilters.filters.split(':');
|
||||
dataHash[key] = value;
|
||||
}
|
||||
dataHash.per_page = 30;
|
||||
dataHash.page = paginationNumber;
|
||||
dataHash.channel_text = query;
|
||||
if (searchType === 'discoverable') {
|
||||
dataHash.user_id = 'all';
|
||||
}
|
||||
const responsePromise = fetchSearch('chat_channels', dataHash);
|
||||
|
||||
return responsePromise.then((response) => {
|
||||
const channels = response.result;
|
||||
return createDataHash(additionalFilters, searchParams).then((response) => {
|
||||
if (
|
||||
retrievalID === null ||
|
||||
channels.filter((e) => e.chat_channel_id === retrievalID).length === 1
|
||||
searchParams.retrievalID === null ||
|
||||
response.result.filter(
|
||||
(e) => e.chat_channel_id === searchParams.retrievalID,
|
||||
).length === 1
|
||||
) {
|
||||
successCb(channels, query);
|
||||
successCb(response.result, searchParams.query);
|
||||
} else {
|
||||
fetch(
|
||||
`/chat_channel_memberships/find_by_chat_channel_id?chat_channel_id=${retrievalID}`,
|
||||
`/chat_channel_memberships/find_by_chat_channel_id?chat_channel_id=${searchParams.retrievalID}`,
|
||||
{
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
|
|
@ -137,8 +122,8 @@ export function getChannels(
|
|||
)
|
||||
.then((individualResponse) => individualResponse.json())
|
||||
.then((json) => {
|
||||
channels.unshift(json);
|
||||
successCb(channels, query);
|
||||
response.result.unshift(json);
|
||||
successCb(response.result, searchParams.query);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -187,6 +172,17 @@ export function getChannelInvites(successCb, failureCb) {
|
|||
.catch(failureCb);
|
||||
}
|
||||
|
||||
export function getJoiningRequest(successCb, failureCb) {
|
||||
fetch('/chat_channels?state=joining_request', {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
credentials: 'same-origin',
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(successCb)
|
||||
.catch(failureCb);
|
||||
}
|
||||
|
||||
export function sendChannelInviteAction(id, action, successCb, failureCb) {
|
||||
fetch(`/chat_channel_memberships/${id}`, {
|
||||
method: 'PUT',
|
||||
|
|
@ -226,23 +222,3 @@ export function deleteMessage(messageId, successCb, failureCb) {
|
|||
.then(successCb)
|
||||
.catch(failureCb);
|
||||
}
|
||||
|
||||
export function sendChannelRequest(id, successCb, failureCb) {
|
||||
fetch(`/join_chat_channel`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'X-CSRF-Token': window.csrfToken,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
chat_channel_membership: {
|
||||
chat_channel_id: id,
|
||||
},
|
||||
}),
|
||||
credentials: 'same-origin',
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(successCb)
|
||||
.catch(failureCb);
|
||||
}
|
||||
71
app/javascript/chat/actions/requestActions.js
Normal file
71
app/javascript/chat/actions/requestActions.js
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
export function rejectJoiningRequest(
|
||||
channelId,
|
||||
membershipId,
|
||||
successCb,
|
||||
failureCb,
|
||||
) {
|
||||
fetch(`/chat_channel_memberships/remove_membership`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'X-CSRF-Token': window.csrfToken,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
status: 'pending',
|
||||
chat_channel_id: channelId,
|
||||
membership_id: membershipId,
|
||||
}),
|
||||
credentials: 'same-origin',
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(successCb)
|
||||
.catch(failureCb);
|
||||
}
|
||||
|
||||
export function acceptJoiningRequest(
|
||||
channelId,
|
||||
membershipId,
|
||||
successCb,
|
||||
failureCb,
|
||||
) {
|
||||
fetch(`/chat_channel_memberships/add_membership`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'X-CSRF-Token': window.csrfToken,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
chat_channel_id: channelId,
|
||||
membership_id: membershipId,
|
||||
chat_channel_membership: {
|
||||
user_action: 'accept',
|
||||
},
|
||||
}),
|
||||
credentials: 'same-origin',
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(successCb)
|
||||
.catch(failureCb);
|
||||
}
|
||||
|
||||
export function sendChannelRequest(id, successCb, failureCb) {
|
||||
fetch(`/join_chat_channel`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'X-CSRF-Token': window.csrfToken,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
chat_channel_membership: {
|
||||
chat_channel_id: id,
|
||||
},
|
||||
}),
|
||||
credentials: 'same-origin',
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(successCb)
|
||||
.catch(failureCb);
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ const Channels = ({
|
|||
unopenedChannelIds,
|
||||
handleSwitchChannel,
|
||||
expanded,
|
||||
filterQuery,
|
||||
filterQuery = '',
|
||||
channelsLoaded,
|
||||
currentUserId,
|
||||
triggerActiveContent,
|
||||
|
|
@ -32,7 +32,6 @@ const Channels = ({
|
|||
);
|
||||
},
|
||||
);
|
||||
|
||||
const channels = sortedChatChannels.activeChannels.map((channel) => {
|
||||
const isActive = parseInt(activeChannelId, 10) === channel.chat_channel_id;
|
||||
const isUnopened =
|
||||
|
|
|
|||
|
|
@ -11,11 +11,16 @@ import {
|
|||
getUnopenedChannelIds,
|
||||
getContent,
|
||||
getChannelInvites,
|
||||
getJoiningRequest,
|
||||
sendChannelInviteAction,
|
||||
deleteMessage,
|
||||
editMessage,
|
||||
} from './actions/actions';
|
||||
import {
|
||||
sendChannelRequest,
|
||||
} from './actions';
|
||||
rejectJoiningRequest,
|
||||
acceptJoiningRequest,
|
||||
} from './actions/requestActions';
|
||||
import {
|
||||
hideMessages,
|
||||
scrollToBottom,
|
||||
|
|
@ -46,7 +51,6 @@ export default class Chat extends Component {
|
|||
super(props);
|
||||
const chatChannels = JSON.parse(props.chatChannels);
|
||||
const chatOptions = JSON.parse(props.chatOptions);
|
||||
|
||||
this.debouncedChannelFilter = debounceAction(
|
||||
this.triggerChannelFilter.bind(this),
|
||||
);
|
||||
|
|
@ -75,6 +79,7 @@ export default class Chat extends Component {
|
|||
isMobileDevice: typeof window.orientation !== 'undefined',
|
||||
subscribedPusherChannels: [],
|
||||
inviteChannels: [],
|
||||
joiningRequests: [],
|
||||
messageOffset: 0,
|
||||
showDeleteModal: false,
|
||||
messageDeleteId: null,
|
||||
|
|
@ -128,14 +133,13 @@ export default class Chat extends Component {
|
|||
channelTypeFilter === 'all'
|
||||
? {}
|
||||
: { filters: `channel_type:${channelTypeFilter}` };
|
||||
getChannels(
|
||||
'',
|
||||
activeChannelId,
|
||||
'',
|
||||
channelPaginationNum,
|
||||
filters,
|
||||
this.loadChannels,
|
||||
);
|
||||
const searchParams = {
|
||||
query: '',
|
||||
retrievalID: activeChannelId,
|
||||
searchType: '',
|
||||
paginationNumber: channelPaginationNum,
|
||||
};
|
||||
getChannels(searchParams, filters, this.loadChannels);
|
||||
getUnopenedChannelIds(this.markUnopenedChannelIds);
|
||||
}
|
||||
if (!isMobileDevice) {
|
||||
|
|
@ -147,6 +151,7 @@ export default class Chat extends Component {
|
|||
.addEventListener('scroll', this.handleChannelScroll);
|
||||
}
|
||||
getChannelInvites(this.handleChannelInvites, null);
|
||||
getJoiningRequest(this.handleChannelJoiningRequest, null);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
|
|
@ -521,14 +526,13 @@ export default class Chat extends Component {
|
|||
channelTypeFilter === 'all'
|
||||
? {}
|
||||
: { filters: `channel_type:${channelTypeFilter}` };
|
||||
getChannels(
|
||||
filterQuery,
|
||||
activeChannelId,
|
||||
'',
|
||||
channelPaginationNum,
|
||||
filters,
|
||||
this.loadPaginatedChannels,
|
||||
);
|
||||
const searchParams = {
|
||||
query: filterQuery,
|
||||
retrievalID: activeChannelId,
|
||||
searchType: '',
|
||||
paginationNumber: channelPaginationNum,
|
||||
};
|
||||
getChannels(searchParams, filters, this.loadPaginatedChannels);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -536,6 +540,10 @@ export default class Chat extends Component {
|
|||
this.setState({ inviteChannels: response });
|
||||
};
|
||||
|
||||
handleChannelJoiningRequest = (res) => {
|
||||
this.setState({ joiningRequests: res });
|
||||
};
|
||||
|
||||
handleKeyDown = (e) => {
|
||||
const { showMemberlist, activeContent, activeChannelId } = this.state;
|
||||
const enterPressed = e.keyCode === 13;
|
||||
|
|
@ -823,6 +831,24 @@ export default class Chat extends Component {
|
|||
}
|
||||
};
|
||||
|
||||
handleRequestRejection = (e) => {
|
||||
rejectJoiningRequest(
|
||||
e.target.dataset.channelId,
|
||||
e.target.dataset.membershipId,
|
||||
this.handleJoiningManagerSuccess(e.target.dataset.membershipId),
|
||||
null,
|
||||
);
|
||||
};
|
||||
|
||||
handleRequestApproval = (e) => {
|
||||
acceptJoiningRequest(
|
||||
e.target.dataset.channelId,
|
||||
e.target.dataset.membershipId,
|
||||
this.handleJoiningManagerSuccess(e.target.dataset.membershipId),
|
||||
null,
|
||||
);
|
||||
};
|
||||
|
||||
triggerActiveContent = (e) => {
|
||||
if (
|
||||
// Trying to open in new tab
|
||||
|
|
@ -859,6 +885,13 @@ export default class Chat extends Component {
|
|||
handleJoiningRequest: this.handleJoiningRequest,
|
||||
type_of: 'channel-request',
|
||||
});
|
||||
} else if (content === 'sidecar-joining-request-manager') {
|
||||
this.setActiveContent({
|
||||
data: this.state.joiningRequests,
|
||||
type_of: 'channel-request-manager',
|
||||
handleRequestRejection: this.handleRequestRejection,
|
||||
handleRequestApproval: this.handleRequestApproval,
|
||||
});
|
||||
} else if (content === 'sidecar_all') {
|
||||
this.setActiveContentState(activeChannelId, {
|
||||
type_of: 'loading-post',
|
||||
|
|
@ -970,17 +1003,17 @@ export default class Chat extends Component {
|
|||
fetchingPaginatedChannels: false,
|
||||
});
|
||||
const filters = type === 'all' ? {} : { filters: `channel_type:${type}` };
|
||||
const searchParams = {
|
||||
query: filterQuery,
|
||||
retrievalID: null,
|
||||
searchType: '',
|
||||
paginationNumber: 0,
|
||||
};
|
||||
if (filterQuery && type !== 'direct') {
|
||||
getChannels(
|
||||
filterQuery,
|
||||
null,
|
||||
'discoverable',
|
||||
0,
|
||||
filters,
|
||||
this.loadChannels,
|
||||
);
|
||||
searchParams.searchType = 'discoverable';
|
||||
getChannels(searchParams, filters, this.loadChannels);
|
||||
} else {
|
||||
getChannels(filterQuery, null, '', 0, filters, this.loadChannels);
|
||||
getChannels(searchParams, filters, this.loadChannels);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -1083,17 +1116,17 @@ export default class Chat extends Component {
|
|||
channelTypeFilter === 'all'
|
||||
? {}
|
||||
: { filters: `channel_type:${channelTypeFilter}` };
|
||||
const searchParams = {
|
||||
query: e.target.value,
|
||||
retrievalID: null,
|
||||
searchType: '',
|
||||
paginationNumber: 0,
|
||||
};
|
||||
if (e.target.value) {
|
||||
getChannels(
|
||||
e.target.value,
|
||||
null,
|
||||
'discoverable',
|
||||
0,
|
||||
filters,
|
||||
this.loadChannels,
|
||||
);
|
||||
searchParams.searchType = 'discoverable';
|
||||
getChannels(searchParams, filters, this.loadChannels);
|
||||
} else {
|
||||
getChannels(e.target.value, null, '', 0, filters, this.loadChannels);
|
||||
getChannels(searchParams, filters, this.loadChannels);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -1120,7 +1153,13 @@ export default class Chat extends Component {
|
|||
document.getElementById('chatchannelsearchbar').focus();
|
||||
}, 100);
|
||||
} else {
|
||||
getChannels('', null, '', 0, '', this.loadChannels);
|
||||
const searchParams = {
|
||||
query: '',
|
||||
retrievalID: null,
|
||||
searchType: '',
|
||||
paginationNumber: 0,
|
||||
};
|
||||
getChannels(searchParams, this.loadChannels);
|
||||
this.setState({ filterQuery: '' });
|
||||
}
|
||||
this.setState({ searchShowing: !this.state.searchShowing });
|
||||
|
|
@ -1133,6 +1172,7 @@ export default class Chat extends Component {
|
|||
const notificationsButton = '';
|
||||
let notificationsState = '';
|
||||
let invitesButton = '';
|
||||
let joiningRequestButton = '';
|
||||
if (notificationsPermission === 'granted') {
|
||||
notificationsState = (
|
||||
<div className="chat_chatconfig chat_chatconfig--on">
|
||||
|
|
@ -1164,6 +1204,23 @@ export default class Chat extends Component {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
if (state.joiningRequests.length > 0) {
|
||||
joiningRequestButton = (
|
||||
<div className="chat__channelinvitationsindicator">
|
||||
<button
|
||||
onClick={this.triggerActiveContent}
|
||||
data-content="sidecar-joining-request-manager"
|
||||
type="button"
|
||||
>
|
||||
<span role="img" aria-label="emoji">
|
||||
👋
|
||||
</span>
|
||||
{' '}
|
||||
New Requests
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (state.expanded) {
|
||||
return (
|
||||
<div className="chat__channels chat__channels--expanded">
|
||||
|
|
@ -1186,6 +1243,7 @@ export default class Chat extends Component {
|
|||
''
|
||||
)}
|
||||
{invitesButton}
|
||||
{joiningRequestButton}
|
||||
<div className="chat__channeltypefilter">
|
||||
<button
|
||||
className="chat__channelssearchtoggle"
|
||||
|
|
@ -1641,6 +1699,17 @@ export default class Chat extends Component {
|
|||
);
|
||||
};
|
||||
|
||||
handleJoiningManagerSuccess = (membershipId) => {
|
||||
const { activeChannelId } = this.state;
|
||||
this.setState({
|
||||
joiningRequests: this.state.joiningRequests.filter(
|
||||
(req) => req.membership_id !== parseInt(membershipId, 10),
|
||||
),
|
||||
});
|
||||
this.setActiveContentState(activeChannelId, null);
|
||||
this.setState({ fullscreenContent: null });
|
||||
};
|
||||
|
||||
handleJoiningRequestSuccess = () => {
|
||||
const { activeChannelId } = this.state;
|
||||
this.setActiveContentState(activeChannelId, null);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import { h, Component } from 'preact';
|
|||
import PropTypes from 'prop-types';
|
||||
import Article from './article';
|
||||
import ChannelRequest from './channelRequest';
|
||||
import RequestManager from './requestManager';
|
||||
|
||||
export default class Content extends Component {
|
||||
static propTypes = {
|
||||
resource: PropTypes.object,
|
||||
|
|
@ -81,4 +83,13 @@ function display(props) {
|
|||
/>
|
||||
);
|
||||
}
|
||||
if (resource.type_of === 'channel-request-manager') {
|
||||
return (
|
||||
<RequestManager
|
||||
resource={resource.data}
|
||||
handleRequestRejection={resource.handleRequestRejection}
|
||||
handleRequestApproval={resource.handleRequestApproval}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
56
app/javascript/chat/requestManager.jsx
Normal file
56
app/javascript/chat/requestManager.jsx
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import { h } from 'preact';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const RequestManager = ({
|
||||
resource: data,
|
||||
handleRequestRejection,
|
||||
handleRequestApproval,
|
||||
}) => {
|
||||
return (
|
||||
<div className="activechatchannel__activeArticle activesendrequest">
|
||||
<div className="request_manager_header crayons-card mb-6 grid grid-flow-row gap-6 p-6">
|
||||
<h1>Joining Request</h1>
|
||||
<h3>Manage request comming to all the channels</h3>
|
||||
{data.map((request) => (
|
||||
<div className="crayons-field">
|
||||
<h1>{request.channel_name}</h1>
|
||||
<div className="request-card">
|
||||
<p>{request.member_name}</p>
|
||||
<div className="action">
|
||||
<button
|
||||
type="button"
|
||||
className="crayons-btn crayons-btn--s crayons-btn--danger"
|
||||
onClick={handleRequestRejection}
|
||||
data-channel-id={request.id}
|
||||
data-membership-id={request.membership_id}
|
||||
>
|
||||
{' '}
|
||||
Reject
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="crayons-btn crayons-btn--s"
|
||||
onClick={handleRequestApproval}
|
||||
data-channel-id={request.id}
|
||||
data-membership-id={request.membership_id}
|
||||
>
|
||||
{' '}
|
||||
Accept
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
RequestManager.propTypes = {
|
||||
resource: PropTypes.shape({
|
||||
data: PropTypes.object,
|
||||
}).isRequired,
|
||||
handleRequestRejection: PropTypes.func.isRequired,
|
||||
handleRequestApproval: PropTypes.func.isRequired,
|
||||
};
|
||||
export default RequestManager;
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
import { fetchSearch } from '../utilities/search';
|
||||
|
||||
import 'intersection-observer';
|
||||
|
||||
export function getCsrfToken() {
|
||||
|
|
@ -107,3 +109,18 @@ export const channelSorter = (channels, currentUserId, filterQuery) => {
|
|||
.filter((channel) => !ChannelIds[0].includes(channel.chat_channel_id));
|
||||
return { activeChannels, discoverableChannels };
|
||||
};
|
||||
|
||||
export const createDataHash = (additionalFilters, searchParams) => {
|
||||
const dataHash = {};
|
||||
if (additionalFilters.filters) {
|
||||
const [key, value] = additionalFilters.filters.split(':');
|
||||
dataHash[key] = value;
|
||||
}
|
||||
dataHash.per_page = 30;
|
||||
dataHash.page = searchParams.paginationNumber;
|
||||
dataHash.channel_text = searchParams.query;
|
||||
if (searchParams.searchType === 'discoverable') {
|
||||
dataHash.user_id = 'all';
|
||||
}
|
||||
return fetchSearch('chat_channels', dataHash);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -16,4 +16,5 @@ json.array!(memberships) do |membership|
|
|||
|
||||
json.adjusted_slug membership.chat_channel.adjusted_slug(current_user)
|
||||
json.membership_id membership.id
|
||||
json.member_name membership.user.username
|
||||
end
|
||||
|
|
|
|||
|
|
@ -57,8 +57,7 @@ RSpec.describe "ChatChannels", type: :request do
|
|||
membership.chat_channel.update(discoverable: true)
|
||||
sign_in user
|
||||
get "/chat_channels?state=joining_request"
|
||||
expect(response.body).to include("\"status\":\"joining_request\"")
|
||||
expect(response.body).to include("joining_requests")
|
||||
expect(response.body).to include("\"member_name\":\"#{membership.user.username}\"")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue