[deploy] Refactor 🚀 : Replaced Chat Channel Setting page with Preact component. (#8271)
* 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 * 🚀 Action to open setting page added * 🛠 Dummy page added for chat setting * add JSON routes for chat channel settings * Integrate chat channel settings API with UI * 🐞 Channel List bug in mobile view * 🛠 Request Managers Frontend ready * changes in routes for adding and removing remembership * 🚀 New routes implemented for request manager * fix issues * 🐞 Channel List bug in mobile view * 🛠 Request Managers Frontend ready * changes in routes for adding and removing remembership * 🚀 New routes implemented for request manager * fix issues * 🛠Optimizing again for CodeClimate * 🚀 Action to open setting page added * 🛠 Dummy page added for chat setting * add JSON routes for chat channel settings * Integrate chat channel settings API with UI * Fix PR requested changes * Add JSDoc documentation to exported functions * fix/add rspec test cases * refactor channelSettinngs render part * add test cases for chat channel settings component * fix code-climate * add rest component tests * add more test coverage * fix code-climate bugs * add API function test Co-authored-by: Sarthak Sharma <7lovesharma7@gmail.com> Co-authored-by: Parasgr-code <paras.gaur@skynox.tech>
This commit is contained in:
parent
2d2cfe4cab
commit
e78a0078a7
57 changed files with 2677 additions and 353 deletions
|
|
@ -1274,3 +1274,7 @@
|
|||
.action button:nth-child(2n) {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.channel_details {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@ class ChatChannelMembershipsController < ApplicationController
|
|||
after_action :verify_authorized, except: :join_channel
|
||||
include MessagesHelper
|
||||
|
||||
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
|
||||
rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
|
||||
|
||||
def index
|
||||
skip_authorization
|
||||
@pending_invites = current_user.chat_channel_memberships.includes(:chat_channel).where(status: "pending")
|
||||
|
|
@ -17,26 +20,28 @@ class ChatChannelMembershipsController < ApplicationController
|
|||
)
|
||||
end
|
||||
|
||||
def edit
|
||||
def chat_channel_info
|
||||
@membership = ChatChannelMembership.find(params[:id])
|
||||
@channel = @membership.chat_channel
|
||||
authorize @membership
|
||||
@channel = @membership.chat_channel
|
||||
data = ChatChannelDetailPresenter.new(@channel, @membership).as_json
|
||||
|
||||
render json: { success: true, result: data, message: "" }, success: :ok
|
||||
end
|
||||
|
||||
def create
|
||||
membership_params = params[:chat_channel_membership]
|
||||
@chat_channel = ChatChannel.find(membership_params[:chat_channel_id])
|
||||
authorize @chat_channel, :update?
|
||||
usernames = membership_params[:invitation_usernames].split(",").map { |username| username.strip.delete("@") }
|
||||
def create_membership_request
|
||||
chat_channel = ChatChannel.find_by(id: channel_membership_request_params[:chat_channel_id])
|
||||
authorize chat_channel, :update?
|
||||
usernames = channel_membership_request_params[:invitation_usernames].split(",").map { |username| username.strip.delete("@") }
|
||||
users = User.where(username: usernames)
|
||||
invitations_sent = @chat_channel.invite_users(users: users, membership_role: "member", inviter: current_user)
|
||||
flash[:settings_notice] = if invitations_sent.zero?
|
||||
"No invitations sent. Check for username typos."
|
||||
else
|
||||
"#{invitations_sent} #{'invitation'.pluralize(invitations_sent)} sent."
|
||||
end
|
||||
membership = @chat_channel.chat_channel_memberships.find_by!(user: current_user)
|
||||
redirect_to edit_chat_channel_membership_path(membership)
|
||||
invitations_sent = chat_channel.invite_users(users: users, membership_role: "member", inviter: current_user)
|
||||
message = if invitations_sent.zero?
|
||||
"No invitations sent. Check for username typos."
|
||||
else
|
||||
"#{invitations_sent} #{'invitation'.pluralize(invitations_sent)} sent."
|
||||
end
|
||||
|
||||
render json: { success: true, message: message, data: {} }, status: :ok
|
||||
end
|
||||
|
||||
def join_channel
|
||||
|
|
@ -50,7 +55,7 @@ class ChatChannelMembershipsController < ApplicationController
|
|||
status = membership.save
|
||||
end
|
||||
if status
|
||||
render json: { status: "success", message: "Request Sent" }
|
||||
render json: { status: "success", message: "Request Sent" }, status: :ok
|
||||
else
|
||||
render json: { status: 400, message: "Unable to join channel" }, status: :bad_request
|
||||
end
|
||||
|
|
@ -60,20 +65,16 @@ 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."
|
||||
message = "Invitation removed."
|
||||
else
|
||||
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
|
||||
message = "Removed #{@chat_channel_membership.user.name}"
|
||||
end
|
||||
redirect_to edit_chat_channel_membership_path(membership)
|
||||
|
||||
render json: { status: "success", message: message, success: true }, status: :ok
|
||||
end
|
||||
|
||||
def add_membership
|
||||
|
|
@ -86,24 +87,32 @@ class ChatChannelMembershipsController < ApplicationController
|
|||
def update
|
||||
@chat_channel_membership = ChatChannelMembership.find(params[:id])
|
||||
authorize @chat_channel_membership
|
||||
if permitted_params[:user_action].present?
|
||||
respond_to_invitation(@chat_channel_membership.status)
|
||||
respond_to_invitation(@chat_channel_membership.status)
|
||||
end
|
||||
|
||||
def update_membership
|
||||
@chat_channel_membership = ChatChannelMembership.find(params[:id])
|
||||
authorize @chat_channel_membership
|
||||
@chat_channel_membership.update(permitted_params)
|
||||
if @chat_channel_membership.errors.any?
|
||||
render json: { success: false, errors: @chat_channel_membership.errors.full_messages, message: "Failed to update settings." }, status: :bad_request
|
||||
else
|
||||
@chat_channel_membership.update(permitted_params)
|
||||
flash[:settings_notice] = "Personal settings updated."
|
||||
redirect_to edit_chat_channel_membership_path(@chat_channel_membership.id)
|
||||
render json: { success: true, message: "Personal settings updated." }, status: :ok
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@chat_channel_membership = ChatChannelMembership.find(params[:id])
|
||||
authorize @chat_channel_membership
|
||||
channel_name = @chat_channel_membership.chat_channel.channel_name
|
||||
send_chat_action_message("@#{current_user.username} left #{@chat_channel_membership.channel_name}", current_user, @chat_channel_membership.chat_channel_id, "left_channel")
|
||||
@chat_channel_membership.update(status: "left_channel")
|
||||
@chat_channels_memberships = []
|
||||
flash[:settings_notice] = "You have left the channel #{channel_name}. It may take a moment to be removed from your list."
|
||||
redirect_to chat_channel_memberships_path
|
||||
def leave_membership
|
||||
chat_channel_membership = ChatChannelMembership.find_by(id: params[:id])
|
||||
authorize chat_channel_membership
|
||||
channel_name = chat_channel_membership.chat_channel.channel_name
|
||||
send_chat_action_message("@#{current_user.username} left #{chat_channel_membership.channel_name}", current_user, chat_channel_membership.chat_channel_id, "left_channel")
|
||||
chat_channel_membership.update(status: "left_channel")
|
||||
message = "You have left the channel #{channel_name}. It may take a moment to be removed from your list."
|
||||
if chat_channel_membership.errors.any?
|
||||
render json: { success: false, message: "Failed to update membership", errors: chat_channel_membership.errors.full_messages }, status: :bad_request
|
||||
else
|
||||
render json: { success: true, message: message }, status: :ok
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
|
@ -112,6 +121,10 @@ class ChatChannelMembershipsController < ApplicationController
|
|||
params.require(:chat_channel_membership).permit(:user_action, :show_global_badge_notification)
|
||||
end
|
||||
|
||||
def channel_membership_request_params
|
||||
params.require(:chat_channel_membership).permit(:chat_channel_id, :invitation_usernames)
|
||||
end
|
||||
|
||||
def respond_to_invitation(previous_status)
|
||||
if permitted_params[:user_action] == "accept"
|
||||
@chat_channel_membership.update(status: "active")
|
||||
|
|
@ -121,21 +134,20 @@ class ChatChannelMembershipsController < ApplicationController
|
|||
flash[:settings_notice] = "Invitation to #{channel_name} accepted. It may take a moment to show up in your list."
|
||||
else
|
||||
send_chat_action_message("@#{current_user.username} added @#{@chat_channel_membership.user.username}", current_user, @chat_channel_membership.chat_channel_id, "joined")
|
||||
|
||||
NotifyMailer.with(membership: @chat_channel_membership, inviter: @chat_channel_membership.user).channel_invite_email.deliver_later
|
||||
|
||||
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)
|
||||
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")
|
||||
flash[:settings_notice] = "Invitation rejected."
|
||||
end
|
||||
redirect_to chat_channel_memberships_path
|
||||
|
||||
membership_user = MembershipUserPresenter.new(@chat_channel_membership).as_json
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to chat_channel_memberships_path }
|
||||
format.json { render json: { status: "success", message: flash[:settings_notice], success: true, membership: membership_user }, status: :ok }
|
||||
end
|
||||
end
|
||||
|
||||
def send_chat_action_message(message, user, channel_id, action)
|
||||
|
|
@ -143,4 +155,12 @@ class ChatChannelMembershipsController < ApplicationController
|
|||
message = Message.create("message_markdown" => message, "user_id" => user.id, "chat_channel_id" => channel_id, "chat_action" => action)
|
||||
pusher_message_created(false, message, temp_message_id)
|
||||
end
|
||||
|
||||
def user_not_authorized
|
||||
render json: { success: false, message: "User not authorized" }, status: :unauthorized
|
||||
end
|
||||
|
||||
def record_not_found
|
||||
render json: { success: false, message: "not found" }, status: :not_found
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
class ChatChannelsController < ApplicationController
|
||||
before_action :authenticate_user!, only: %i[moderate]
|
||||
before_action :set_channel, only: %i[show update open moderate]
|
||||
before_action :set_channel, only: %i[show update update_channel open moderate]
|
||||
after_action :verify_authorized
|
||||
|
||||
def index
|
||||
|
|
@ -33,21 +33,36 @@ class ChatChannelsController < ApplicationController
|
|||
end
|
||||
|
||||
def update
|
||||
if ChatChannelUpdateService.new(@chat_channel, chat_channel_params).update
|
||||
if !chat_channel_params[:discoverable].to_i.zero?
|
||||
ChatChannelMembership.create(user_id: SiteConfig.mascot_user_id, chat_channel_id: @chat_channel.id, role: "member", status: "active")
|
||||
chat_channel = ChatChannelUpdateService.perform(@chat_channel, chat_channel_params)
|
||||
if chat_channel.errors.any?
|
||||
flash[:error] = chat_channel.errors.full_messages.to_sentence
|
||||
else
|
||||
if chat_channel_params[:discoverable].to_i.zero?
|
||||
ChatChannelMembership.create(user_id: SiteConfig.mascot_user_id, chat_channel_id: chat_channel.id, role: "member", status: "active")
|
||||
else
|
||||
ChatChannelMembership.find_by(user_id: SiteConfig.mascot_user_id)&.destroy
|
||||
end
|
||||
flash[:settings_notice] = "Channel settings updated."
|
||||
else
|
||||
default_error_message = "Channel settings updation failed. Try again later."
|
||||
flash[:error] = @chat_channel.errors_as_sentence.presence || default_error_message
|
||||
end
|
||||
current_user_membership = @chat_channel.mod_memberships.find_by!(user: current_user)
|
||||
|
||||
redirect_to edit_chat_channel_membership_path(current_user_membership)
|
||||
end
|
||||
|
||||
def update_channel
|
||||
chat_channel = ChatChannelUpdateService.perform(@chat_channel, chat_channel_params)
|
||||
if chat_channel.errors.any?
|
||||
render json: { success: false, errors: chat_channel.errors.full_messages, message: "Channel settings updation failed. Try again later." }, success: :bad_request
|
||||
else
|
||||
if chat_channel_params[:discoverable]
|
||||
ChatChannelMembership.create(user_id: SiteConfig.mascot_user_id, chat_channel_id: chat_channel.id, role: "member", status: "active")
|
||||
else
|
||||
ChatChannelMembership.find_by(user_id: SiteConfig.mascot_user_id)&.destroy
|
||||
end
|
||||
render json: { success: true, message: "Channel settings updated.", data: {} }, success: :ok
|
||||
end
|
||||
end
|
||||
|
||||
def open
|
||||
membership = @chat_channel.chat_channel_memberships.where(user_id: current_user.id).first
|
||||
membership.update(last_opened_at: 1.second.from_now, has_unopened_messages: false)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
import { h } from 'preact';
|
||||
import PropTypes from 'prop-types';
|
||||
import Membership from './Membership';
|
||||
|
||||
const ActiveMembershipSection = ({
|
||||
activeMemberships,
|
||||
removeMembership,
|
||||
currentMembershipRole,
|
||||
}) => {
|
||||
return (
|
||||
<div className="p-4 grid gap-2 crayons-card mb-4">
|
||||
<h3 className="mb-2 active_members">Members</h3>
|
||||
{activeMemberships && activeMemberships.length > 0
|
||||
? activeMemberships.map((pendingMembership) => (
|
||||
<Membership
|
||||
membership={pendingMembership}
|
||||
removeMembership={removeMembership}
|
||||
membershipType="active"
|
||||
currentMembershipRole={currentMembershipRole}
|
||||
className="active-member"
|
||||
/>
|
||||
))
|
||||
: null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ActiveMembershipSection.propTypes = {
|
||||
activeMemberships: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
membership_id: PropTypes.number.isRequired,
|
||||
user_id: PropTypes.number.isRequired,
|
||||
role: PropTypes.string.isRequired,
|
||||
image: PropTypes.string.isRequired,
|
||||
username: PropTypes.string.isRequired,
|
||||
status: PropTypes.string.isRequired,
|
||||
}),
|
||||
).isRequired,
|
||||
removeMembership: PropTypes.func.isRequired,
|
||||
currentMembershipRole: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default ActiveMembershipSection;
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import { h } from 'preact';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const ChannelDescriptionSection = ({
|
||||
channelName,
|
||||
channelDescription,
|
||||
currentMembershipRole
|
||||
}) => {
|
||||
return (
|
||||
<div className="p-4 grid gap-2 crayons-card mb-4 channel_details">
|
||||
<h1 className="mb-1 channel_title">{channelName}</h1>
|
||||
<p>{channelDescription}</p>
|
||||
<p className="fw-bold">
|
||||
You are a channel
|
||||
{' '}
|
||||
{currentMembershipRole}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
ChannelDescriptionSection.propTypes = {
|
||||
channelName: PropTypes.string.isRequired,
|
||||
currentMembershipRole: PropTypes.string.isRequired,
|
||||
channelDescription: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
export default ChannelDescriptionSection;
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
import { h } from 'preact';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import ActiveMembershipSection from './ActiveMembershipsSection';
|
||||
import PendingMembershipSection from './PendingMembershipSection'
|
||||
import RequestedMembershipSection from './RequestedMembershipSection';
|
||||
|
||||
const ChatChannelMembershipSection = ({
|
||||
pendingMemberships,
|
||||
requestedMemberships,
|
||||
chatChannelAcceptMembership,
|
||||
activeMemberships,
|
||||
removeMembership,
|
||||
currentMembershipRole
|
||||
}) => {
|
||||
return (
|
||||
<div className="membership-list">
|
||||
<ActiveMembershipSection
|
||||
activeMemberships={activeMemberships}
|
||||
removeMembership={removeMembership}
|
||||
currentMembershipRole={currentMembershipRole}
|
||||
/>
|
||||
<PendingMembershipSection
|
||||
pendingMemberships={pendingMemberships}
|
||||
removeMembership={removeMembership}
|
||||
currentMembershipRole={currentMembershipRole}
|
||||
/>
|
||||
<RequestedMembershipSection
|
||||
requestedMemberships={requestedMemberships}
|
||||
removeMembership={removeMembership}
|
||||
chatChannelAcceptMembership={chatChannelAcceptMembership}
|
||||
currentMembershipRole={currentMembershipRole}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
ChatChannelMembershipSection.propTypes = {
|
||||
pendingMemberships: PropTypes.arrayOf(PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
membership_id: PropTypes.number.isRequired,
|
||||
user_id: PropTypes.number.isRequired,
|
||||
role: PropTypes.string.isRequired,
|
||||
image: PropTypes.string.isRequired,
|
||||
username: PropTypes.string.isRequired,
|
||||
})).isRequired,
|
||||
requestedMemberships: PropTypes.arrayOf(PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
membership_id: PropTypes.number.isRequired,
|
||||
user_id: PropTypes.number.isRequired,
|
||||
role: PropTypes.string.isRequired,
|
||||
image: PropTypes.string.isRequired,
|
||||
username: PropTypes.string.isRequired,
|
||||
})).isRequired,
|
||||
activeMemberships: PropTypes.arrayOf(PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
membership_id: PropTypes.number.isRequired,
|
||||
user_id: PropTypes.number.isRequired,
|
||||
role: PropTypes.string.isRequired,
|
||||
image: PropTypes.string.isRequired,
|
||||
username: PropTypes.string.isRequired,
|
||||
status: PropTypes.string.isRequired,
|
||||
})).isRequired,
|
||||
removeMembership: PropTypes.func.isRequired,
|
||||
chatChannelAcceptMembership: PropTypes.func.isRequired,
|
||||
currentMembershipRole: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
export default ChatChannelMembershipSection;
|
||||
378
app/javascript/chat/ChatChannelSettings/ChatChannelSettings.jsx
Normal file
378
app/javascript/chat/ChatChannelSettings/ChatChannelSettings.jsx
Normal file
|
|
@ -0,0 +1,378 @@
|
|||
import { h, Component, render } from 'preact';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {
|
||||
getChannelDetails,
|
||||
updatePersonalChatChannelNotificationSettings,
|
||||
rejectChatChannelJoiningRequest,
|
||||
acceptChatChannelJoiningRequest,
|
||||
updateChatChannelDescription,
|
||||
sendChatChannelInvitation,
|
||||
leaveChatChannelMembership,
|
||||
} from '../actions/chat_channel_setting_actions';
|
||||
|
||||
import { Snackbar, addSnackbarItem } from '../../Snackbar';
|
||||
import ModSection from './ModSection';
|
||||
import PersonalSettings from './PersonalSetting';
|
||||
import LeaveMembershipSection from './LeaveMembershipSection';
|
||||
import ModFaqSection from './ModFaqSection';
|
||||
import ChannelDescriptionSection from './ChannelDescriptionSection';
|
||||
import ChatChannelMembershipSection from './ChatChannelMembershipSection';
|
||||
|
||||
const snackZone = document.getElementById('snack-zone');
|
||||
|
||||
render(<Snackbar lifespan="3" />, snackZone, null);
|
||||
|
||||
export default class ChatChannelSettings extends Component {
|
||||
static propTypes = {
|
||||
activeMembershipId: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
successMessages: null,
|
||||
errorMessages: null,
|
||||
activeMemberships: [],
|
||||
pendingMemberships: [],
|
||||
requestedMemberships: [],
|
||||
chatChannel: null,
|
||||
currentMembership: null,
|
||||
activeMembershipId: props.activeMembershipId,
|
||||
channelDescription: null,
|
||||
channelDiscoverable: null,
|
||||
invitationUsernames: null,
|
||||
showGlobalBadgeNotification: null,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { activeMembershipId } = this.state;
|
||||
|
||||
getChannelDetails(activeMembershipId)
|
||||
.then((response) => {
|
||||
if (response.success) {
|
||||
const { result } = response;
|
||||
this.setState({
|
||||
chatChannel: result.chat_channel,
|
||||
activeMemberships: result.memberships.active,
|
||||
pendingMemberships: result.memberships.pending,
|
||||
requestedMemberships: result.memberships.requested,
|
||||
currentMembership: result.current_membership,
|
||||
channelDescription: result.chat_channel.description,
|
||||
channelDiscoverable: result.chat_channel.discoverable,
|
||||
showGlobalBadgeNotification:
|
||||
result.current_membership.show_global_badge_notification,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
successMessages: null,
|
||||
errorMessages: response.message,
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.setState({
|
||||
successMessages: null,
|
||||
errorMessages: error.message,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
handleDescriptionChange = (e) => {
|
||||
const description = e.target.value;
|
||||
this.setState({
|
||||
channelDescription: description,
|
||||
});
|
||||
};
|
||||
|
||||
handlePersonChatChennelSetting = (e) => {
|
||||
const status = e.target.checked;
|
||||
this.setState({
|
||||
showGlobalBadgeNotification: status,
|
||||
});
|
||||
};
|
||||
|
||||
updateCurrentMembershipNotificationSettings = async () => {
|
||||
const { currentMembership, showGlobalBadgeNotification } = this.state;
|
||||
const response = await updatePersonalChatChannelNotificationSettings(
|
||||
currentMembership.id,
|
||||
showGlobalBadgeNotification,
|
||||
);
|
||||
const { message } = response;
|
||||
if (response.success) {
|
||||
this.setState((prevState) => {
|
||||
return {
|
||||
errorMessages: null,
|
||||
successMessages: response.message,
|
||||
currentMembership: {
|
||||
...prevState.currentMembership,
|
||||
show_global_badge_notification: showGlobalBadgeNotification,
|
||||
},
|
||||
};
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
successMessages: null,
|
||||
errorMessages: response.message,
|
||||
showGlobalBadgeNotification:
|
||||
currentMembership.show_global_badge_notification,
|
||||
});
|
||||
}
|
||||
addSnackbarItem({ message });
|
||||
};
|
||||
|
||||
chatChannelRemoveMembership = async (membershipId, membershipStatus) => {
|
||||
const { chatChannel } = this.state;
|
||||
const response = await rejectChatChannelJoiningRequest(
|
||||
chatChannel.id,
|
||||
membershipId,
|
||||
membershipStatus,
|
||||
);
|
||||
return response;
|
||||
};
|
||||
|
||||
filterMemberships = (memberships, membershipId) => {
|
||||
const filteredMembership = memberships.filter(
|
||||
(membership) => membership.membership_id !== Number(membershipId),
|
||||
);
|
||||
return filteredMembership;
|
||||
};
|
||||
|
||||
removeMembership = async (e) => {
|
||||
const { membershipId, membershipStatus } = e.target.dataset;
|
||||
const response = await this.chatChannelRemoveMembership(
|
||||
membershipId,
|
||||
membershipStatus,
|
||||
);
|
||||
const { message } = response;
|
||||
this.updateMemberships(membershipId, response, membershipStatus);
|
||||
addSnackbarItem({ message });
|
||||
};
|
||||
|
||||
updateMemberships = (membershipId, response, membershipStatus) => {
|
||||
if (response.success) {
|
||||
this.setState((prevState) => {
|
||||
return {
|
||||
errorMessages: null,
|
||||
successMessages: response.message,
|
||||
activeMemberships:
|
||||
membershipStatus === 'active'
|
||||
? this.filterMemberships(
|
||||
prevState.activeMemberships,
|
||||
membershipId,
|
||||
)
|
||||
: prevState.activeMemberships,
|
||||
pendingMemberships:
|
||||
membershipStatus === 'pending'
|
||||
? this.filterMemberships(
|
||||
prevState.pendingMemberships,
|
||||
membershipId,
|
||||
)
|
||||
: prevState.pendingMemberships,
|
||||
requestedMemberships:
|
||||
membershipStatus === 'joining_request'
|
||||
? this.filterMemberships(
|
||||
prevState.requestedMemberships,
|
||||
membershipId,
|
||||
)
|
||||
: prevState.requestedMembership,
|
||||
};
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
successMessages: null,
|
||||
errorMessages: response.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
chatChannelAcceptMembership = async (e) => {
|
||||
const { chatChannel } = this.state;
|
||||
const { membershipId } = e.target.dataset;
|
||||
const response = await acceptChatChannelJoiningRequest(
|
||||
chatChannel.id,
|
||||
membershipId,
|
||||
);
|
||||
const { message } = response;
|
||||
if (response.success) {
|
||||
this.setState((prevState) => {
|
||||
const filteredRequestedMemberships = prevState.requestedMemberships.filter(
|
||||
(requestedMembership) =>
|
||||
requestedMembership.membership_id !== Number(membershipId),
|
||||
);
|
||||
const updatedActiveMembership = [
|
||||
...prevState.activeMemberships,
|
||||
response.membership,
|
||||
];
|
||||
return {
|
||||
errorMessages: null,
|
||||
successMessages: response.message,
|
||||
requestedMemberships: filteredRequestedMemberships,
|
||||
activeMemberships: updatedActiveMembership,
|
||||
};
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
successMessages: null,
|
||||
errorMessages: response.message,
|
||||
});
|
||||
}
|
||||
addSnackbarItem({ message });
|
||||
};
|
||||
|
||||
handleChannelDiscoverableStatus = (e) => {
|
||||
const status = e.target.checked;
|
||||
this.setState({
|
||||
channelDiscoverable: status,
|
||||
});
|
||||
};
|
||||
|
||||
handleChannelDescriptionChanges = async () => {
|
||||
const { chatChannel, channelDescription, channelDiscoverable } = this.state;
|
||||
const { id } = chatChannel;
|
||||
const response = await updateChatChannelDescription(
|
||||
id,
|
||||
channelDescription,
|
||||
channelDiscoverable,
|
||||
);
|
||||
const { message } = response;
|
||||
|
||||
if (response.success) {
|
||||
this.componentDidMount();
|
||||
this.setState((prevState) => {
|
||||
return {
|
||||
errorMessages: null,
|
||||
successMessages: response.message,
|
||||
chatChannel: {
|
||||
...prevState,
|
||||
description: channelDescription,
|
||||
discoverable: channelDiscoverable,
|
||||
},
|
||||
};
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
successMessages: null,
|
||||
errorMessages: response.message,
|
||||
channelDiscoverable: chatChannel.discoverable,
|
||||
});
|
||||
}
|
||||
addSnackbarItem({ message });
|
||||
};
|
||||
|
||||
handleInvitationUsernames = (e) => {
|
||||
const invitationUsernameValue = e.target.value;
|
||||
this.setState({
|
||||
invitationUsernames: invitationUsernameValue,
|
||||
});
|
||||
};
|
||||
|
||||
handleChatChannelInvitations = async () => {
|
||||
const { invitationUsernames, chatChannel } = this.state;
|
||||
const { id } = chatChannel;
|
||||
const response = await sendChatChannelInvitation(id, invitationUsernames);
|
||||
const { message } = response;
|
||||
if (response.success) {
|
||||
this.componentDidMount();
|
||||
this.setState({
|
||||
errorMessages: null,
|
||||
successMessages: response.message,
|
||||
invitationUsernames: null,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
successMessages: null,
|
||||
errorMessages: response.message,
|
||||
});
|
||||
}
|
||||
addSnackbarItem({ message });
|
||||
};
|
||||
|
||||
handleleaveChatChannelMembership = async () => {
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
const actionStatus = confirm(
|
||||
'Are you absolutely sure you want to leave this channel? This action is permanent.',
|
||||
);
|
||||
const { currentMembership } = this.state;
|
||||
if (actionStatus) {
|
||||
const response = await leaveChatChannelMembership(currentMembership.id);
|
||||
if (response.success) {
|
||||
this.componentDidMount();
|
||||
} else {
|
||||
this.setState({
|
||||
successMessages: null,
|
||||
errorMessages: response.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
chatChannel,
|
||||
currentMembership,
|
||||
activeMemberships,
|
||||
pendingMemberships,
|
||||
requestedMemberships,
|
||||
channelDescription,
|
||||
channelDiscoverable,
|
||||
invitationUsernames,
|
||||
showGlobalBadgeNotification,
|
||||
} = this.state;
|
||||
|
||||
if (!chatChannel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="activechatchannel__activeArticle channel_settings">
|
||||
<div className="p-4">
|
||||
<ChannelDescriptionSection
|
||||
channelName={chatChannel.name}
|
||||
channelDescription={chatChannel.description}
|
||||
currentMembershipRole={currentMembership.role}
|
||||
/>
|
||||
<ChatChannelMembershipSection
|
||||
currentMembershipRole={currentMembership.role}
|
||||
activeMemberships={activeMemberships}
|
||||
removeMembership={this.removeMembership}
|
||||
pendingMemberships={pendingMemberships}
|
||||
requestedMemberships={requestedMemberships}
|
||||
chatChannelAcceptMembership={this.chatChannelAcceptMembership}
|
||||
/>
|
||||
<ModSection
|
||||
invitationUsernames={invitationUsernames}
|
||||
handleInvitationUsernames={this.handleInvitationUsernames}
|
||||
handleChatChannelInvitations={this.handleChatChannelInvitations}
|
||||
channelDescription={channelDescription}
|
||||
handleDescriptionChange={this.handleDescriptionChange}
|
||||
channelDiscoverable={channelDiscoverable}
|
||||
handleChannelDiscoverableStatus={
|
||||
this.handleChannelDiscoverableStatus
|
||||
}
|
||||
handleChannelDescriptionChanges={
|
||||
this.handleChannelDescriptionChanges
|
||||
}
|
||||
currentMembershipRole={currentMembership.role}
|
||||
/>
|
||||
<PersonalSettings
|
||||
updateCurrentMembershipNotificationSettings={
|
||||
this.updateCurrentMembershipNotificationSettings
|
||||
}
|
||||
showGlobalBadgeNotification={showGlobalBadgeNotification}
|
||||
handlePersonChatChennelSetting={this.handlePersonChatChennelSetting}
|
||||
/>
|
||||
<LeaveMembershipSection
|
||||
currentMembershipRole={currentMembership.role}
|
||||
handleleaveChatChannelMembership={
|
||||
this.handleleaveChatChannelMembership
|
||||
}
|
||||
/>
|
||||
<ModFaqSection currentMembershipRole={currentMembership.role} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
47
app/javascript/chat/ChatChannelSettings/InviateForm.jsx
Normal file
47
app/javascript/chat/ChatChannelSettings/InviateForm.jsx
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import { h } from 'preact';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const InviteForm = ({
|
||||
handleChatChannelInvitations,
|
||||
invitationUsernames,
|
||||
handleInvitationUsernames
|
||||
}) => {
|
||||
return (
|
||||
<div className="crayons-card p-4 grid gap-2 mb-4">
|
||||
<div className="crayons-field">
|
||||
<label
|
||||
className="crayons-field__label invitation_form_title"
|
||||
htmlFor="chat_channel_membership_Usernames to Invite"
|
||||
>
|
||||
Usernames to invite
|
||||
</label>
|
||||
<input
|
||||
placeholder="Comma separated"
|
||||
className="crayons-textfield"
|
||||
type="text"
|
||||
value={invitationUsernames}
|
||||
name="chat_channel_membership[invitation_usernames]"
|
||||
id="chat_channel_membership_invitation_usernames"
|
||||
onChange={handleInvitationUsernames}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
className="crayons-btn"
|
||||
type="submit"
|
||||
onClick={handleChatChannelInvitations}
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
InviteForm.propTypes = {
|
||||
handleInvitationUsernames: PropTypes.func.isRequired,
|
||||
handleChatChannelInvitations: PropTypes.func.isRequired,
|
||||
invitationUsernames: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default InviteForm;
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import { h } from 'preact';
|
||||
import PropsType from 'prop-types';
|
||||
|
||||
const LeaveMembershipSection = ({
|
||||
currentMembershipRole,
|
||||
handleleaveChatChannelMembership,
|
||||
}) => {
|
||||
if (currentMembershipRole !== 'member') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="crayons-card p-4 grid gap-2 mb-4 leave_membership_section">
|
||||
<h3>Danger Zone</h3>
|
||||
<div>
|
||||
<button
|
||||
className="crayons-btn crayons-btn--danger leave_button"
|
||||
type="submit"
|
||||
onClick={handleleaveChatChannelMembership}
|
||||
>
|
||||
Leave Channel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
LeaveMembershipSection.propTypes = {
|
||||
currentMembershipRole: PropsType.string.isRequired,
|
||||
handleleaveChatChannelMembership: PropsType.func.isRequired,
|
||||
};
|
||||
|
||||
export default LeaveMembershipSection;
|
||||
67
app/javascript/chat/ChatChannelSettings/Membership.jsx
Normal file
67
app/javascript/chat/ChatChannelSettings/Membership.jsx
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import { h } from 'preact';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const Membership = ({
|
||||
membership,
|
||||
removeMembership,
|
||||
membershipType,
|
||||
chatChannelAcceptMembership,
|
||||
currentMembershipRole,
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<a href={`/${membership.username}`}>
|
||||
<span className="crayons-avatar crayons-avatar--l mr-3">
|
||||
<img
|
||||
className="crayons-avatar__image align-middle"
|
||||
role="presentation"
|
||||
src={membership.image}
|
||||
alt={`${membership.name} profile`}
|
||||
/>
|
||||
</span>
|
||||
<span className="mr-2 user_name">{membership.name}</span>
|
||||
</a>
|
||||
{membershipType === 'requested' ? (
|
||||
<button
|
||||
className="crayons-btn crayons-btn--icon-rounded crayons-btn--ghost add-membership"
|
||||
type="button"
|
||||
onClick={chatChannelAcceptMembership}
|
||||
data-membership-id={membership.membership_id}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
) : null}
|
||||
{membership.role !== 'mod' && currentMembershipRole === 'mod' ? (
|
||||
<button
|
||||
className="crayons-btn crayons-btn--icon-rounded crayons-btn--ghost remove-membership"
|
||||
type="button"
|
||||
onClick={removeMembership}
|
||||
data-membership-id={membership.membership_id}
|
||||
data-membership-status={membership.status}
|
||||
>
|
||||
x
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Membership.propTypes = {
|
||||
membership: PropTypes.objectOf(
|
||||
PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
membership_id: PropTypes.number.isRequired,
|
||||
user_id: PropTypes.number.isRequired,
|
||||
role: PropTypes.string.isRequired,
|
||||
image: PropTypes.string.isRequired,
|
||||
username: PropTypes.string.isRequired,
|
||||
status: PropTypes.string.isRequired,
|
||||
}),
|
||||
).isRequired,
|
||||
removeMembership: PropTypes.func.isRequired,
|
||||
membershipType: PropTypes.func.isRequired,
|
||||
chatChannelAcceptMembership: PropTypes.func.isRequired,
|
||||
currentMembershipRole: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default Membership;
|
||||
30
app/javascript/chat/ChatChannelSettings/ModFaqSection.jsx
Normal file
30
app/javascript/chat/ChatChannelSettings/ModFaqSection.jsx
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { h } from 'preact';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const ModFaqSection = ({ currentMembershipRole }) => {
|
||||
if (currentMembershipRole !== 'mod') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="crayons-card grid gap-2 p-4 faq-section">
|
||||
<p className="contact-details">
|
||||
Questions about Connect Channel moderation? Contact
|
||||
<a
|
||||
href="mailto:yo@dev.to"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="mx-2 url-link"
|
||||
>
|
||||
yo@dev.to
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ModFaqSection.propTypes = {
|
||||
currentMembershipRole: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default ModFaqSection;
|
||||
52
app/javascript/chat/ChatChannelSettings/ModSection.jsx
Normal file
52
app/javascript/chat/ChatChannelSettings/ModSection.jsx
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import { h } from 'preact';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import InviteForm from './InviateForm';
|
||||
import SettingsFrom from './SettingsForm';
|
||||
|
||||
const ModSection = ({
|
||||
handleChatChannelInvitations,
|
||||
invitationUsernames,
|
||||
handleInvitationUsernames,
|
||||
channelDescription,
|
||||
handleDescriptionChange,
|
||||
channelDiscoverable,
|
||||
handleChannelDiscoverableStatus,
|
||||
handleChannelDescriptionChanges,
|
||||
currentMembershipRole,
|
||||
}) => {
|
||||
if (currentMembershipRole !== 'mod') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mod-section">
|
||||
<InviteForm
|
||||
handleInvitationUsernames={handleInvitationUsernames}
|
||||
invitationUsernames={invitationUsernames}
|
||||
handleChatChannelInvitations={handleChatChannelInvitations}
|
||||
/>
|
||||
<SettingsFrom
|
||||
channelDescription={channelDescription}
|
||||
handleDescriptionChange={handleDescriptionChange}
|
||||
channelDiscoverable={channelDiscoverable}
|
||||
handleChannelDiscoverableStatus={handleChannelDiscoverableStatus}
|
||||
handleChannelDescriptionChanges={handleChannelDescriptionChanges}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ModSection.propTypes = {
|
||||
handleInvitationUsernames: PropTypes.func.isRequired,
|
||||
handleChatChannelInvitations: PropTypes.func.isRequired,
|
||||
invitationUsernames: PropTypes.func.isRequired,
|
||||
channelDescription: PropTypes.string.isRequired,
|
||||
handleDescriptionChange: PropTypes.func.isRequired,
|
||||
handleChannelDiscoverableStatus: PropTypes.func.isRequired,
|
||||
handleChannelDescriptionChanges: PropTypes.func.isRequired,
|
||||
channelDiscoverable: PropTypes.bool.isRequired,
|
||||
currentMembershipRole: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default ModSection;
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
import { h } from 'preact';
|
||||
import PropTypes from 'prop-types';
|
||||
import Membership from './Membership';
|
||||
|
||||
const PendingMembershipSection = ({
|
||||
pendingMemberships,
|
||||
removeMembership,
|
||||
currentMembershipRole,
|
||||
}) => {
|
||||
if (currentMembershipRole !== 'mod') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-4 grid gap-2 crayons-card mb-4 pending_memberships">
|
||||
<h3 className="mb-2">Pending Invitations</h3>
|
||||
{pendingMemberships && pendingMemberships.length > 0
|
||||
? pendingMemberships.map((pendingMembership) => (
|
||||
<Membership
|
||||
membership={pendingMembership}
|
||||
removeMembership={removeMembership}
|
||||
membershipType="pending"
|
||||
currentMembershipRole={currentMembershipRole}
|
||||
className="pending-member"
|
||||
/>
|
||||
))
|
||||
: null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
PendingMembershipSection.propTypes = {
|
||||
pendingMemberships: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
membership_id: PropTypes.number.isRequired,
|
||||
user_id: PropTypes.number.isRequired,
|
||||
role: PropTypes.string.isRequired,
|
||||
image: PropTypes.string.isRequired,
|
||||
username: PropTypes.string.isRequired,
|
||||
status: PropTypes.string.isRequired,
|
||||
}),
|
||||
).isRequired,
|
||||
removeMembership: PropTypes.func.isRequired,
|
||||
currentMembershipRole: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default PendingMembershipSection;
|
||||
44
app/javascript/chat/ChatChannelSettings/PersonalSetting.jsx
Normal file
44
app/javascript/chat/ChatChannelSettings/PersonalSetting.jsx
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import { h } from 'preact';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const PersonalSettings = ({
|
||||
handlePersonChatChennelSetting,
|
||||
showGlobalBadgeNotification,
|
||||
updateCurrentMembershipNotificationSettings,
|
||||
}) => {
|
||||
return (
|
||||
<div className="crayons-card p-4 grid gap-2 mb-4 personl-settings">
|
||||
<h3>Personal Settings</h3>
|
||||
<h4>Notifications</h4>
|
||||
<div className="crayons-field crayons-field--checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="c3"
|
||||
className="crayons-checkbox"
|
||||
checked={showGlobalBadgeNotification}
|
||||
onChange={handlePersonChatChennelSetting}
|
||||
/>
|
||||
<label htmlFor="c3" className="crayons-field__label">
|
||||
Receive Notifications for New Messages
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
className="crayons-btn"
|
||||
type="submit"
|
||||
onClick={updateCurrentMembershipNotificationSettings}
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
PersonalSettings.propTypes = {
|
||||
updateCurrentMembershipNotificationSettings: PropTypes.func.isRequired,
|
||||
showGlobalBadgeNotification: PropTypes.bool.isRequired,
|
||||
handlePersonChatChennelSetting: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default PersonalSettings;
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
import { h } from 'preact';
|
||||
import PropTypes from 'prop-types';
|
||||
import Membership from './Membership';
|
||||
|
||||
const RequestedMembershipSection = ({
|
||||
requestedMemberships,
|
||||
removeMembership,
|
||||
chatChannelAcceptMembership,
|
||||
currentMembershipRole,
|
||||
}) => {
|
||||
if (currentMembershipRole !== 'mod') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-4 grid gap-2 crayons-card mb-4">
|
||||
<h3 className="mb-2 requested_memberships">Joining Request</h3>
|
||||
{requestedMemberships && requestedMemberships.length > 0
|
||||
? requestedMemberships.map((pendingMembership) => (
|
||||
<Membership
|
||||
membership={pendingMembership}
|
||||
removeMembership={removeMembership}
|
||||
chatChannelAcceptMembership={chatChannelAcceptMembership}
|
||||
membershipType="requested"
|
||||
currentMembershipRole={currentMembershipRole}
|
||||
className="requested-member"
|
||||
/>
|
||||
))
|
||||
: null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
RequestedMembershipSection.propTypes = {
|
||||
requestedMemberships: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
membership_id: PropTypes.number.isRequired,
|
||||
user_id: PropTypes.number.isRequired,
|
||||
role: PropTypes.string.isRequired,
|
||||
image: PropTypes.string.isRequired,
|
||||
username: PropTypes.string.isRequired,
|
||||
status: PropTypes.string.isRequired,
|
||||
}),
|
||||
).isRequired,
|
||||
removeMembership: PropTypes.func.isRequired,
|
||||
chatChannelAcceptMembership: PropTypes.func.isRequired,
|
||||
currentMembershipRole: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default RequestedMembershipSection;
|
||||
62
app/javascript/chat/ChatChannelSettings/SettingsForm.jsx
Normal file
62
app/javascript/chat/ChatChannelSettings/SettingsForm.jsx
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import { h } from 'preact';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const SettingsFrom = ({
|
||||
channelDescription,
|
||||
handleDescriptionChange,
|
||||
channelDiscoverable,
|
||||
handleChannelDiscoverableStatus,
|
||||
handleChannelDescriptionChanges,
|
||||
}) => {
|
||||
return (
|
||||
<div className="crayons-card p-4 grid gap-2 mb-4 settings-section">
|
||||
<h3>Channel Settings</h3>
|
||||
<div className="crayons-field">
|
||||
<label
|
||||
className="crayons-field__label"
|
||||
htmlFor="chat_channel_description"
|
||||
>
|
||||
Description
|
||||
</label>
|
||||
<textarea
|
||||
className="crayons-textfield"
|
||||
name="description"
|
||||
id="chat_channel_description"
|
||||
value={channelDescription}
|
||||
onChange={handleDescriptionChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="crayons-field crayons-field--checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="c2"
|
||||
className="crayons-checkbox"
|
||||
checked={channelDiscoverable}
|
||||
onChange={handleChannelDiscoverableStatus}
|
||||
/>
|
||||
<label htmlFor="c2" className="crayons-field__label">
|
||||
Channel Discoverable
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
className="crayons-btn"
|
||||
type="submit"
|
||||
onClick={handleChannelDescriptionChanges}
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
SettingsFrom.propTypes = {
|
||||
channelDescription: PropTypes.string.isRequired,
|
||||
handleDescriptionChange: PropTypes.func.isRequired,
|
||||
handleChannelDiscoverableStatus: PropTypes.func.isRequired,
|
||||
handleChannelDescriptionChanges: PropTypes.func.isRequired,
|
||||
channelDiscoverable: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default SettingsFrom;
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<ActiveMembershipsSection /> should render and test snapshot 1`] = `
|
||||
<div
|
||||
class="p-4 grid gap-2 crayons-card mb-4"
|
||||
>
|
||||
<h3
|
||||
class="mb-2 active_members"
|
||||
>
|
||||
Members
|
||||
</h3>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<ChannelDescriptionSection /> should render and test snapshot 1`] = `
|
||||
<div
|
||||
class="p-4 grid gap-2 crayons-card mb-4 channel_details"
|
||||
>
|
||||
<h1
|
||||
class="mb-1 channel_title"
|
||||
>
|
||||
some name
|
||||
</h1>
|
||||
<p>
|
||||
some description
|
||||
</p>
|
||||
<p
|
||||
class="fw-bold"
|
||||
>
|
||||
You are a channel mod
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<ChatChannelMembershipSection /> should render and test snapshot 1`] = `
|
||||
<div
|
||||
class="membership-list"
|
||||
>
|
||||
<div
|
||||
class="p-4 grid gap-2 crayons-card mb-4"
|
||||
>
|
||||
<h3
|
||||
class="mb-2 active_members"
|
||||
>
|
||||
Members
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<ChatChannelSettings /> should render and test snapshot 1`] = `null`;
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<InviteForm /> should render the test snapshot 1`] = `
|
||||
<div
|
||||
class="crayons-card p-4 grid gap-2 mb-4"
|
||||
>
|
||||
<div
|
||||
class="crayons-field"
|
||||
>
|
||||
<label
|
||||
class="crayons-field__label invitation_form_title"
|
||||
htmlFor="chat_channel_membership_Usernames to Invite"
|
||||
>
|
||||
Usernames to invite
|
||||
</label>
|
||||
<input
|
||||
class="crayons-textfield"
|
||||
id="chat_channel_membership_invitation_usernames"
|
||||
name="chat_channel_membership[invitation_usernames]"
|
||||
placeholder="Comma separated"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
class="crayons-btn"
|
||||
type="submit"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<LeaveMembershipSection /> should render and test snapshot 1`] = `
|
||||
<div
|
||||
class="crayons-card p-4 grid gap-2 mb-4 leave_membership_section"
|
||||
>
|
||||
<h3>
|
||||
Danger Zone
|
||||
</h3>
|
||||
<div>
|
||||
<button
|
||||
class="crayons-btn crayons-btn--danger leave_button"
|
||||
type="submit"
|
||||
>
|
||||
Leave Channel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<Membership /> should render and test snapshot 1`] = `
|
||||
<div
|
||||
class="flex items-center"
|
||||
>
|
||||
<a
|
||||
href="/testusername"
|
||||
>
|
||||
<span
|
||||
class="crayons-avatar crayons-avatar--l mr-3"
|
||||
>
|
||||
<img
|
||||
alt="test user profile"
|
||||
class="crayons-avatar__image align-middle"
|
||||
role="presentation"
|
||||
src=""
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="mr-2 user_name"
|
||||
>
|
||||
test user
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<ChannelDescriptionSection /> should render and test snapshot 1`] = `
|
||||
<div
|
||||
class="crayons-card grid gap-2 p-4 faq-section"
|
||||
>
|
||||
<p
|
||||
class="contact-details"
|
||||
>
|
||||
Questions about Connect Channel moderation? Contact
|
||||
<a
|
||||
class="mx-2 url-link"
|
||||
href="mailto:yo@dev.to"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
yo@dev.to
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<ModSection /> should render and test snapshot 1`] = `
|
||||
<div
|
||||
class="mod-section"
|
||||
>
|
||||
<div
|
||||
class="crayons-card p-4 grid gap-2 mb-4"
|
||||
>
|
||||
<div
|
||||
class="crayons-field"
|
||||
>
|
||||
<label
|
||||
class="crayons-field__label invitation_form_title"
|
||||
htmlFor="chat_channel_membership_Usernames to Invite"
|
||||
>
|
||||
Usernames to invite
|
||||
</label>
|
||||
<input
|
||||
class="crayons-textfield"
|
||||
id="chat_channel_membership_invitation_usernames"
|
||||
name="chat_channel_membership[invitation_usernames]"
|
||||
placeholder="Comma separated"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
class="crayons-btn"
|
||||
type="submit"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="crayons-card p-4 grid gap-2 mb-4 settings-section"
|
||||
>
|
||||
<h3>
|
||||
Channel Settings
|
||||
</h3>
|
||||
<div
|
||||
class="crayons-field"
|
||||
>
|
||||
<label
|
||||
class="crayons-field__label"
|
||||
htmlFor="chat_channel_description"
|
||||
>
|
||||
Description
|
||||
</label>
|
||||
<textarea
|
||||
class="crayons-textfield"
|
||||
id="chat_channel_description"
|
||||
name="description"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="crayons-field crayons-field--checkbox"
|
||||
>
|
||||
<input
|
||||
class="crayons-checkbox"
|
||||
id="c2"
|
||||
type="checkbox"
|
||||
/>
|
||||
<label
|
||||
class="crayons-field__label"
|
||||
htmlFor="c2"
|
||||
>
|
||||
Channel Discoverable
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
class="crayons-btn"
|
||||
type="submit"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<PendingMembershipSections /> should render and test snapshot 1`] = `
|
||||
<div
|
||||
class="p-4 grid gap-2 crayons-card mb-4 pending_memberships"
|
||||
>
|
||||
<h3
|
||||
class="mb-2"
|
||||
>
|
||||
Pending Invitations
|
||||
</h3>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<PersonalSettng /> should render and test snapshot 1`] = `
|
||||
<div
|
||||
class="crayons-card p-4 grid gap-2 mb-4 personl-settings"
|
||||
>
|
||||
<h3>
|
||||
Personal Settings
|
||||
</h3>
|
||||
<h4>
|
||||
Notifications
|
||||
</h4>
|
||||
<div
|
||||
class="crayons-field crayons-field--checkbox"
|
||||
>
|
||||
<input
|
||||
checked={true}
|
||||
class="crayons-checkbox"
|
||||
id="c3"
|
||||
type="checkbox"
|
||||
/>
|
||||
<label
|
||||
class="crayons-field__label"
|
||||
htmlFor="c3"
|
||||
>
|
||||
Receive Notifications for New Messages
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
class="crayons-btn"
|
||||
type="submit"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<RequestedMembershipSection /> should render and test snapshot 1`] = `
|
||||
<div
|
||||
class="p-4 grid gap-2 crayons-card mb-4"
|
||||
>
|
||||
<h3
|
||||
class="mb-2 requested_memberships"
|
||||
>
|
||||
Joining Request
|
||||
</h3>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<SettingsForm /> should render and test snapshot 1`] = `
|
||||
<div
|
||||
class="crayons-card p-4 grid gap-2 mb-4 settings-section"
|
||||
>
|
||||
<h3>
|
||||
Channel Settings
|
||||
</h3>
|
||||
<div
|
||||
class="crayons-field"
|
||||
>
|
||||
<label
|
||||
class="crayons-field__label"
|
||||
htmlFor="chat_channel_description"
|
||||
>
|
||||
Description
|
||||
</label>
|
||||
<textarea
|
||||
class="crayons-textfield"
|
||||
id="chat_channel_description"
|
||||
name="description"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="crayons-field crayons-field--checkbox"
|
||||
>
|
||||
<input
|
||||
class="crayons-checkbox"
|
||||
id="c2"
|
||||
type="checkbox"
|
||||
/>
|
||||
<label
|
||||
class="crayons-field__label"
|
||||
htmlFor="c2"
|
||||
>
|
||||
Channel Discoverable
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
class="crayons-btn"
|
||||
type="submit"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
import { h } from 'preact';
|
||||
import render from 'preact-render-to-json';
|
||||
import { shallow } from 'preact-render-spy';
|
||||
import ActiveMembershipsSection from '../ChatChannelSettings/ActiveMembershipsSection';
|
||||
|
||||
const data = {
|
||||
activeMemberships: [],
|
||||
currentMembershipRole: 'mod',
|
||||
};
|
||||
|
||||
const membership = {
|
||||
activeMemberships: [
|
||||
{
|
||||
name: 'test user',
|
||||
username: 'testusername',
|
||||
user_id: '1',
|
||||
membership_id: '2',
|
||||
role: 'mod',
|
||||
status: 'active',
|
||||
image: '',
|
||||
},
|
||||
],
|
||||
membershipType: 'active',
|
||||
currentMembershipRole: 'mod',
|
||||
};
|
||||
|
||||
const getActiveMembershipsSection = (membershipData) => (
|
||||
<ActiveMembershipsSection
|
||||
activeMemberships={membershipData.activeMemberships}
|
||||
currentMembershipRole={membershipData.currentMembershipRole}
|
||||
/>
|
||||
);
|
||||
|
||||
describe('<ActiveMembershipsSection />', () => {
|
||||
it('should render and test snapshot', () => {
|
||||
const tree = render(getActiveMembershipsSection(data));
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should have the elements', () => {
|
||||
const context = shallow(getActiveMembershipsSection(data));
|
||||
|
||||
expect(context.find('.active_members').exists()).toEqual(true);
|
||||
});
|
||||
|
||||
it('should have title', () => {
|
||||
const context = shallow(getActiveMembershipsSection(data));
|
||||
|
||||
expect(context.find('.active_members').text()).toEqual('Members');
|
||||
});
|
||||
|
||||
it('should not render the membership list', () => {
|
||||
const context = shallow(getActiveMembershipsSection(data));
|
||||
|
||||
expect(context.find('.items-center').exists()).toEqual(false);
|
||||
});
|
||||
|
||||
it('should render the membership list', () => {
|
||||
const context = shallow(getActiveMembershipsSection(membership));
|
||||
|
||||
expect(context.find('.active-member').exists()).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
import { h } from 'preact';
|
||||
import render from 'preact-render-to-json';
|
||||
import { shallow } from 'preact-render-spy';
|
||||
import ChannelDescriptionSection from '../ChatChannelSettings/ChannelDescriptionSection';
|
||||
|
||||
const data = {
|
||||
channelName: "some name",
|
||||
channelDescription: "some description",
|
||||
currentMembershipRole: "mod"
|
||||
}
|
||||
|
||||
const getChannelDescriptionSection = (channelDetails) => {
|
||||
return (
|
||||
<ChannelDescriptionSection
|
||||
channelName={channelDetails.channelName}
|
||||
channelDescription={channelDetails.channelDescription}
|
||||
currentMembershipRole={channelDetails.currentMembershipRole}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
describe('<ChannelDescriptionSection />', () => {
|
||||
it ('should render and test snapshot', () => {
|
||||
const tree = render(getChannelDescriptionSection(data));
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
})
|
||||
|
||||
it ("should render the the component", () => {
|
||||
const context = shallow(getChannelDescriptionSection(data));
|
||||
|
||||
expect(context.find('.channel_details').exists()).toEqual(true);
|
||||
})
|
||||
|
||||
it ("should render the same header", () => {
|
||||
const context = shallow(getChannelDescriptionSection(data));
|
||||
expect(context.find('.channel_title').text()).toEqual(data.channelName)
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import { h } from 'preact';
|
||||
import render from 'preact-render-to-json';
|
||||
import { shallow } from 'preact-render-spy';
|
||||
import ChatChannelMembershipSection from '../ChatChannelSettings/ChatChannelMembershipSection';
|
||||
|
||||
const data = {
|
||||
pendingMemberships: [],
|
||||
requestedMemberships: [],
|
||||
activeMemberships: []
|
||||
}
|
||||
|
||||
const getChatChannelMembershipSection = (memberships) => {
|
||||
return (
|
||||
<ChatChannelMembershipSection
|
||||
pendingMemberships={memberships.pendingMemberships}
|
||||
requestedMemberships={memberships.requestedMemberships}
|
||||
activeMemberships={memberships.activeMemberships}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
describe('<ChatChannelMembershipSection />', () => {
|
||||
it ('should render and test snapshot', () => {
|
||||
const tree = render(getChatChannelMembershipSection(data));
|
||||
expect(tree).toMatchSnapshot();
|
||||
})
|
||||
|
||||
it ('should have the proper elements, attributes and values', () => {
|
||||
const context = shallow(getChatChannelMembershipSection(data));
|
||||
|
||||
|
||||
expect(context.find('.membership-list').exists()).toEqual(true)
|
||||
})
|
||||
})
|
||||
203
app/javascript/chat/__tests__/chatChannelSettingActions.test.js
Normal file
203
app/javascript/chat/__tests__/chatChannelSettingActions.test.js
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
import fetch from 'jest-fetch-mock';
|
||||
import {
|
||||
getChannelDetails,
|
||||
updatePersonalChatChannelNotificationSettings,
|
||||
rejectChatChannelJoiningRequest,
|
||||
acceptChatChannelJoiningRequest,
|
||||
updateChatChannelDescription,
|
||||
sendChatChannelInvitation,
|
||||
leaveChatChannelMembership,
|
||||
} from '../actions/chat_channel_setting_actions';
|
||||
|
||||
/* global globalThis */
|
||||
|
||||
describe('Chat cahnnel API requestes', () => {
|
||||
const csrfToken = 'this-is-a-csrf-token';
|
||||
const chanChannelMembershipId = 26; // Just a random chatChannelMembershipId ID.
|
||||
const channelId = 2;
|
||||
|
||||
beforeAll(() => {
|
||||
globalThis.fetch = fetch;
|
||||
globalThis.getCsrfToken = async () => csrfToken;
|
||||
});
|
||||
afterAll(() => {
|
||||
delete globalThis.fetch;
|
||||
delete globalThis.getCsrfToken;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('get chat channel info', () => {
|
||||
it('should have success response with channel details', async () => {
|
||||
const response = {
|
||||
success: true,
|
||||
current_membership: {
|
||||
user_id: 1,
|
||||
id: 2,
|
||||
chat_channel_id: 1,
|
||||
status: 'active',
|
||||
role: 'mod',
|
||||
},
|
||||
chat_channel: {
|
||||
name: 'dummy channel',
|
||||
description: 'some dummy description',
|
||||
discoverable: true,
|
||||
status: 'active',
|
||||
},
|
||||
memberships: {
|
||||
active_memberships: [],
|
||||
pending_memberships: [],
|
||||
requested_memberships: [],
|
||||
},
|
||||
};
|
||||
|
||||
fetch.mockResponse(JSON.stringify(response));
|
||||
|
||||
const chatChannelDetails = await getChannelDetails(
|
||||
chanChannelMembershipId,
|
||||
);
|
||||
expect(chatChannelDetails).toEqual(response);
|
||||
});
|
||||
|
||||
it('not found channel', async () => {
|
||||
const response = {
|
||||
success: false,
|
||||
message: 'not found',
|
||||
};
|
||||
|
||||
fetch.mockResponse(JSON.stringify(response));
|
||||
|
||||
const chatChannelDetails = await getChannelDetails(
|
||||
chanChannelMembershipId,
|
||||
);
|
||||
expect(chatChannelDetails).toEqual(response);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Update chat channel notification', () => {
|
||||
it('should have success', async () => {
|
||||
const response = { success: true, message: 'user settings updated' };
|
||||
fetch.mockResponse(JSON.stringify(response));
|
||||
|
||||
const updateProfile = await updatePersonalChatChannelNotificationSettings(
|
||||
chanChannelMembershipId,
|
||||
true,
|
||||
);
|
||||
expect(updateProfile).toEqual(response);
|
||||
});
|
||||
|
||||
it('should return not found error', async () => {
|
||||
const response = { success: false, message: 'not found' };
|
||||
fetch.mockResponse(JSON.stringify(response));
|
||||
|
||||
const updateProfile = await updatePersonalChatChannelNotificationSettings(
|
||||
'',
|
||||
true,
|
||||
);
|
||||
expect(updateProfile).toEqual(response);
|
||||
});
|
||||
});
|
||||
|
||||
describe('reject chat channel membership', () => {
|
||||
it('should have success', async () => {
|
||||
const response = { success: true, message: 'user removed' };
|
||||
fetch.mockResponse(JSON.stringify(response));
|
||||
|
||||
const result = await rejectChatChannelJoiningRequest(
|
||||
channelId,
|
||||
chanChannelMembershipId,
|
||||
'pending',
|
||||
);
|
||||
expect(result).toEqual(response);
|
||||
});
|
||||
|
||||
it('should return not found error', async () => {
|
||||
const response = { success: false, message: 'not found' };
|
||||
fetch.mockResponse(JSON.stringify(response));
|
||||
|
||||
const result = await rejectChatChannelJoiningRequest('', '', 'pending');
|
||||
expect(result).toEqual(response);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Accept chat channel membership', () => {
|
||||
it('should have success', async () => {
|
||||
const response = { success: true, message: 'added to chat channel' };
|
||||
fetch.mockResponse(JSON.stringify(response));
|
||||
|
||||
const result = await acceptChatChannelJoiningRequest(
|
||||
channelId,
|
||||
chanChannelMembershipId,
|
||||
);
|
||||
expect(result).toEqual(response);
|
||||
});
|
||||
|
||||
it('should return not found error', async () => {
|
||||
const response = { success: false, message: 'not found' };
|
||||
fetch.mockResponse(JSON.stringify(response));
|
||||
|
||||
const result = await acceptChatChannelJoiningRequest('', '');
|
||||
expect(result).toEqual(response);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update chat channel', () => {
|
||||
it('should have success', async () => {
|
||||
const response = { success: true, message: 'channel is updated' };
|
||||
fetch.mockResponse(JSON.stringify(response));
|
||||
|
||||
const result = await updateChatChannelDescription(
|
||||
channelId,
|
||||
'some description',
|
||||
true,
|
||||
);
|
||||
expect(result).toEqual(response);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Send inbvitation for join chat channel', () => {
|
||||
it('should have success', async () => {
|
||||
const response = { success: true, message: 'added to chat channel' };
|
||||
fetch.mockResponse(JSON.stringify(response));
|
||||
|
||||
const result = await sendChatChannelInvitation(channelId, 'dummyuser');
|
||||
expect(result).toEqual(response);
|
||||
});
|
||||
|
||||
it('should not send any invitation', async () => {
|
||||
const response = { success: true, message: 'no invitation sent' };
|
||||
fetch.mockResponse(JSON.stringify(response));
|
||||
|
||||
const result = await sendChatChannelInvitation(channelId, '');
|
||||
expect(result).toEqual(response);
|
||||
});
|
||||
|
||||
it('should return not found error', async () => {
|
||||
const response = { success: false, message: 'not found' };
|
||||
fetch.mockResponse(JSON.stringify(response));
|
||||
|
||||
const result = await sendChatChannelInvitation('', '');
|
||||
expect(result).toEqual(response);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Leave chat channel', () => {
|
||||
it('should have success', async () => {
|
||||
const response = { success: true, message: 'user left the channel' };
|
||||
fetch.mockResponse(JSON.stringify(response));
|
||||
|
||||
const result = await leaveChatChannelMembership(chanChannelMembershipId);
|
||||
expect(result).toEqual(response);
|
||||
});
|
||||
|
||||
it('should return not found error', async () => {
|
||||
const response = { success: false, message: 'not found' };
|
||||
fetch.mockResponse(JSON.stringify(response));
|
||||
|
||||
const result = await leaveChatChannelMembership('');
|
||||
expect(result).toEqual(response);
|
||||
});
|
||||
});
|
||||
});
|
||||
28
app/javascript/chat/__tests__/chatChannelSettings.test.jsx
Normal file
28
app/javascript/chat/__tests__/chatChannelSettings.test.jsx
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { h } from 'preact';
|
||||
import render from 'preact-render-to-json';
|
||||
import { shallow } from 'preact-render-spy';
|
||||
import ChatChannelSettings from '../ChatChannelSettings/ChatChannelSettings';
|
||||
|
||||
const channelDetails = {
|
||||
data: {},
|
||||
activeMembershipId: 12,
|
||||
};
|
||||
|
||||
const getChatChannelSettinsg = (resource) => (
|
||||
<ChatChannelSettings
|
||||
resource={resource.data}
|
||||
activeMembershipId={resource.activeMembershipId}
|
||||
/>
|
||||
);
|
||||
|
||||
describe('<ChatChannelSettings />', () => {
|
||||
it('should render and test snapshot', () => {
|
||||
const tree = render(getChatChannelSettinsg(channelDetails));
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should not render if not channel', () => {
|
||||
const context = shallow(getChatChannelSettinsg(channelDetails));
|
||||
expect(context.find('.channel_settings').exists()).toEqual(false);
|
||||
});
|
||||
});
|
||||
27
app/javascript/chat/__tests__/inviteForm.test.jsx
Normal file
27
app/javascript/chat/__tests__/inviteForm.test.jsx
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { h } from 'preact';
|
||||
import render from 'preact-render-to-json';
|
||||
import { shallow } from 'preact-render-spy';
|
||||
import InviteForm from '../ChatChannelSettings/InviateForm';
|
||||
|
||||
const data = {
|
||||
invitationUsernames: ''
|
||||
}
|
||||
|
||||
const getInviteForm = (invitations) => (
|
||||
<InviteForm
|
||||
invitationUsernames={invitations.invitationUsernames}
|
||||
/>
|
||||
)
|
||||
|
||||
describe('<InviteForm />', () => {
|
||||
it("should render the test snapshot", () => {
|
||||
const tree = render(getInviteForm(data))
|
||||
expect(tree).toMatchSnapshot();
|
||||
})
|
||||
|
||||
it("should have the element", () => {
|
||||
const context = shallow(getInviteForm(data));
|
||||
|
||||
expect(context.find('.invitation_form_title').exists()).toEqual(true)
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
import { h } from 'preact';
|
||||
import render from 'preact-render-to-json';
|
||||
import { shallow } from 'preact-render-spy';
|
||||
import LeaveMembershipSection from '../ChatChannelSettings/LeaveMembershipSection';
|
||||
|
||||
const data = {
|
||||
currentMembershipRole: 'member',
|
||||
};
|
||||
|
||||
const modUser = {
|
||||
currentMembershipRole: 'mod',
|
||||
};
|
||||
|
||||
const getLeaveMembershipSection = (membershipData) => (
|
||||
<LeaveMembershipSection
|
||||
currentMembershipRole={membershipData.currentMembershipRole}
|
||||
/>
|
||||
);
|
||||
|
||||
describe('<LeaveMembershipSection />', () => {
|
||||
it('should render and test snapshot', () => {
|
||||
const tree = render(getLeaveMembershipSection(data));
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should have the elements', () => {
|
||||
const context = shallow(getLeaveMembershipSection(data));
|
||||
expect(context.find('.leave_membership_section').exists()).toEqual(true);
|
||||
});
|
||||
|
||||
it('should have the leave button', () => {
|
||||
const context = shallow(getLeaveMembershipSection(data));
|
||||
expect(context.find('.leave_button').text()).toEqual('Leave Channel');
|
||||
});
|
||||
|
||||
it('should not render', () => {
|
||||
const context = shallow(getLeaveMembershipSection(modUser));
|
||||
expect(context.find('.leave_membership_section').exists()).toEqual(false);
|
||||
});
|
||||
});
|
||||
71
app/javascript/chat/__tests__/membership.test.jsx
Normal file
71
app/javascript/chat/__tests__/membership.test.jsx
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import { h } from 'preact';
|
||||
import render from 'preact-render-to-json';
|
||||
import { shallow } from 'preact-render-spy';
|
||||
import Membership from '../ChatChannelSettings/Membership';
|
||||
|
||||
const modUser = {
|
||||
membership: {
|
||||
name: 'test user',
|
||||
username: 'testusername',
|
||||
user_id: '1',
|
||||
membership_id: '2',
|
||||
role: 'mod',
|
||||
status: 'active',
|
||||
image: '',
|
||||
},
|
||||
membershipType: 'active',
|
||||
currentMembershipRole: 'mod',
|
||||
};
|
||||
|
||||
const memberUser = {
|
||||
membership: {
|
||||
name: 'test user',
|
||||
username: 'testusername',
|
||||
user_id: '1',
|
||||
membership_id: '2',
|
||||
role: 'member',
|
||||
status: 'active',
|
||||
image: '',
|
||||
},
|
||||
membershipType: 'requested',
|
||||
currentMembershipRole: 'mod',
|
||||
};
|
||||
|
||||
const getMembership = (membershipData) => (
|
||||
<Membership
|
||||
membership={membershipData.membership}
|
||||
membershipType={membershipData.membershipType}
|
||||
currentMembershipRole={membershipData.currentMembershipRole}
|
||||
/>
|
||||
);
|
||||
|
||||
describe('<Membership />', () => {
|
||||
it('should render and test snapshot', () => {
|
||||
const tree = render(getMembership(modUser));
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should have the element', () => {
|
||||
const context = shallow(getMembership(modUser));
|
||||
expect(context.find('.user_name').exists()).toEqual(true);
|
||||
});
|
||||
|
||||
it('should have the the same ame', () => {
|
||||
const context = shallow(getMembership(modUser));
|
||||
|
||||
expect(context.find('.user_name').text()).toEqual(modUser.membership.name);
|
||||
});
|
||||
|
||||
it('should not visible remove and add membership button', () => {
|
||||
const context = shallow(getMembership(modUser));
|
||||
|
||||
expect(context.find('.remove-membership').exists()).toEqual(false);
|
||||
expect(context.find('.add-membership').exists()).toEqual(false);
|
||||
});
|
||||
|
||||
it('should add button visible', () => {
|
||||
const context = shallow(getMembership(memberUser));
|
||||
expect(context.find('.add-membership').exists()).toEqual(true);
|
||||
expect(context.find('.remove-membership').exists()).toEqual(true);
|
||||
});
|
||||
});
|
||||
49
app/javascript/chat/__tests__/modFaqSection.test.jsx
Normal file
49
app/javascript/chat/__tests__/modFaqSection.test.jsx
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import { h } from 'preact';
|
||||
import render from 'preact-render-to-json';
|
||||
import { shallow } from 'preact-render-spy';
|
||||
import ModFaqSection from '../ChatChannelSettings/ModFaqSection';
|
||||
|
||||
const data = {
|
||||
currentMembershipRole: 'mod',
|
||||
};
|
||||
|
||||
const memberUser = {
|
||||
currentMembershipRole: 'member',
|
||||
};
|
||||
|
||||
const getModFaqSection = (resource) => {
|
||||
return (
|
||||
<ModFaqSection currentMembershipRole={resource.currentMembershipRole} />
|
||||
);
|
||||
};
|
||||
|
||||
describe('<ChannelDescriptionSection />', () => {
|
||||
it('should render and test snapshot', () => {
|
||||
const tree = render(getModFaqSection(data));
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render the the component', () => {
|
||||
const context = shallow(getModFaqSection(data));
|
||||
|
||||
expect(context.find('.faq-section').exists()).toEqual(true);
|
||||
});
|
||||
|
||||
it('should have the contact details', () => {
|
||||
const context = shallow(getModFaqSection(data));
|
||||
|
||||
expect(context.find('.contact-details').exists()).toEqual(true);
|
||||
});
|
||||
|
||||
it('should render the same link text', () => {
|
||||
const context = shallow(getModFaqSection(data));
|
||||
|
||||
expect(context.find('.url-link').text()).toEqual('yo@dev.to');
|
||||
});
|
||||
|
||||
it('should not render the element', () => {
|
||||
const context = shallow(getModFaqSection(memberUser));
|
||||
expect(context.find('.faq-section').exists()).toEqual(false);
|
||||
});
|
||||
});
|
||||
36
app/javascript/chat/__tests__/modSection.test.jsx
Normal file
36
app/javascript/chat/__tests__/modSection.test.jsx
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import { h } from 'preact';
|
||||
import render from 'preact-render-to-json';
|
||||
import { shallow } from 'preact-render-spy';
|
||||
import ModSection from '../ChatChannelSettings/ModSection';
|
||||
|
||||
const modUser = {
|
||||
currentMembershipRole: 'mod',
|
||||
};
|
||||
|
||||
const memberUser = {
|
||||
currentMembershipRole: 'member',
|
||||
};
|
||||
|
||||
const getModSection = (resource) => {
|
||||
return <ModSection currentMembershipRole={resource.currentMembershipRole} />;
|
||||
};
|
||||
|
||||
describe('<ModSection />', () => {
|
||||
it('should render and test snapshot', () => {
|
||||
const tree = render(getModSection(modUser));
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render the the component', () => {
|
||||
const context = shallow(getModSection(modUser));
|
||||
|
||||
expect(context.find('.mod-section').exists()).toEqual(true);
|
||||
});
|
||||
|
||||
it('should not render the the component', () => {
|
||||
const context = shallow(getModSection(memberUser));
|
||||
|
||||
expect(context.find('.mod-section').exists()).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
import { h } from 'preact';
|
||||
import render from 'preact-render-to-json';
|
||||
import { shallow } from 'preact-render-spy';
|
||||
import PendingMembershipSections from '../ChatChannelSettings/PendingMembershipSection';
|
||||
|
||||
const data = {
|
||||
pendingMemberships: [],
|
||||
currentMembershipRole: 'mod',
|
||||
};
|
||||
|
||||
const membership = {
|
||||
pendingMemberships: [
|
||||
{
|
||||
name: 'test user',
|
||||
username: 'testusername',
|
||||
user_id: '1',
|
||||
membership_id: '2',
|
||||
role: 'member',
|
||||
status: 'pending',
|
||||
image: '',
|
||||
},
|
||||
],
|
||||
membershipType: 'pending',
|
||||
currentMembershipRole: 'mod',
|
||||
};
|
||||
|
||||
const getPendingMembershipSections = (membershipData) => (
|
||||
<PendingMembershipSections
|
||||
pendingMemberships={membershipData.pendingMemberships}
|
||||
currentMembershipRole={membershipData.currentMembershipRole}
|
||||
/>
|
||||
);
|
||||
|
||||
describe('<PendingMembershipSections />', () => {
|
||||
it('should render and test snapshot', () => {
|
||||
const tree = render(getPendingMembershipSections(data));
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should have the elements', () => {
|
||||
const context = shallow(getPendingMembershipSections(data));
|
||||
|
||||
expect(context.find('.pending_memberships').exists()).toEqual(true);
|
||||
});
|
||||
|
||||
it('should not render the membership list', () => {
|
||||
const context = shallow(getPendingMembershipSections(data));
|
||||
|
||||
expect(context.find('.membership-list').exists()).toEqual(false);
|
||||
});
|
||||
|
||||
it('should render the membership list', () => {
|
||||
const context = shallow(getPendingMembershipSections(membership));
|
||||
|
||||
expect(context.find('.pending-member').exists()).toEqual(true);
|
||||
});
|
||||
});
|
||||
30
app/javascript/chat/__tests__/personalSetting.test.jsx
Normal file
30
app/javascript/chat/__tests__/personalSetting.test.jsx
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { h } from 'preact';
|
||||
import render from 'preact-render-to-json';
|
||||
import { shallow } from 'preact-render-spy';
|
||||
import PersonalSettng from '../ChatChannelSettings/PersonalSetting';
|
||||
|
||||
const data = {
|
||||
showGlobalBadgeNotification: true,
|
||||
};
|
||||
|
||||
const getPersonalSettng = (channelDetails) => {
|
||||
return (
|
||||
<PersonalSettng
|
||||
showGlobalBadgeNotification={channelDetails.showGlobalBadgeNotification}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
describe('<PersonalSettng />', () => {
|
||||
it('should render and test snapshot', () => {
|
||||
const tree = render(getPersonalSettng(data));
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render the the component', () => {
|
||||
const context = shallow(getPersonalSettng(data));
|
||||
|
||||
expect(context.find('.personl-settings').exists()).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
import { h } from 'preact';
|
||||
import render from 'preact-render-to-json';
|
||||
import { shallow } from 'preact-render-spy';
|
||||
import RequestedMembershipSection from '../ChatChannelSettings/RequestedMembershipSection';
|
||||
|
||||
const data = {
|
||||
requestedMemberships: [],
|
||||
currentMembershipRole: 'mod',
|
||||
};
|
||||
|
||||
const membership = {
|
||||
requestedMemberships: [
|
||||
{
|
||||
name: 'test user',
|
||||
username: 'testusername',
|
||||
user_id: '1',
|
||||
membership_id: '2',
|
||||
role: 'member',
|
||||
status: 'requested',
|
||||
image: '',
|
||||
},
|
||||
],
|
||||
membershipType: 'requested',
|
||||
currentMembershipRole: 'mod',
|
||||
};
|
||||
|
||||
const getRequestedMembershipSection = (membershipData) => (
|
||||
<RequestedMembershipSection
|
||||
requestedMemberships={membershipData.requestedMemberships}
|
||||
currentMembershipRole={membershipData.currentMembershipRole}
|
||||
/>
|
||||
);
|
||||
|
||||
describe('<RequestedMembershipSection />', () => {
|
||||
it('should render and test snapshot', () => {
|
||||
const tree = render(getRequestedMembershipSection(data));
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should have the elements', () => {
|
||||
const context = shallow(getRequestedMembershipSection(data));
|
||||
|
||||
expect(context.find('.requested_memberships').exists()).toEqual(true);
|
||||
});
|
||||
|
||||
it('should not render the membership list', () => {
|
||||
const context = shallow(getRequestedMembershipSection(data));
|
||||
|
||||
expect(context.find('.items-center').exists()).toEqual(false);
|
||||
});
|
||||
|
||||
it('should render the membership list', () => {
|
||||
const context = shallow(getRequestedMembershipSection(membership));
|
||||
|
||||
expect(context.find('.requested-member').exists()).toEqual(true);
|
||||
});
|
||||
});
|
||||
30
app/javascript/chat/__tests__/settingsForm.test.jsx
Normal file
30
app/javascript/chat/__tests__/settingsForm.test.jsx
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { h } from 'preact';
|
||||
import render from 'preact-render-to-json';
|
||||
import { shallow } from 'preact-render-spy';
|
||||
import SettingsForm from '../ChatChannelSettings/SettingsForm';
|
||||
|
||||
const data = {
|
||||
channelDescription: 'some description test',
|
||||
channelDiscoverable: true,
|
||||
};
|
||||
|
||||
const getSettingsForm = (channelDetails) => {
|
||||
return (
|
||||
<SettingsForm
|
||||
showGlobalBadgeNotification={channelDetails.showGlobalBadgeNotification}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
describe('<SettingsForm />', () => {
|
||||
it('should render and test snapshot', () => {
|
||||
const tree = render(getSettingsForm(data));
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render the the component', () => {
|
||||
const context = shallow(getSettingsForm(data));
|
||||
|
||||
expect(context.find('.settings-section').exists()).toEqual(true);
|
||||
});
|
||||
});
|
||||
151
app/javascript/chat/actions/chat_channel_setting_actions.js
Normal file
151
app/javascript/chat/actions/chat_channel_setting_actions.js
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
import { request } from '../../utilities/http';
|
||||
|
||||
/**
|
||||
* This function will get all details of the chat channel accrding to the membership role.
|
||||
*
|
||||
* @param {number} chatChannelMembershipId Current User chat channel membership ID
|
||||
*/
|
||||
export async function getChannelDetails(chatChannelMembershipId) {
|
||||
const response = await request(
|
||||
`/chat_channel_memberships/chat_channel_info/${chatChannelMembershipId}`,
|
||||
{
|
||||
credentials: 'same-origin',
|
||||
},
|
||||
);
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is used to update the notification settings.
|
||||
*
|
||||
* @param {number} membershipId Current user Chat Channel membership Id.
|
||||
* @param {boolean} notificationBadge Boolean value for the notification
|
||||
*/
|
||||
export async function updatePersonalChatChannelNotificationSettings(
|
||||
membershipId,
|
||||
notificationBadge,
|
||||
) {
|
||||
const response = await request(
|
||||
`/chat_channel_memberships/update_membership/${membershipId}`,
|
||||
{
|
||||
method: 'PATCH',
|
||||
body: {
|
||||
chat_channel_membership: {
|
||||
show_global_badge_notification: notificationBadge,
|
||||
},
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
},
|
||||
);
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is used to reject chat channel joining request & pending requests.
|
||||
*
|
||||
* @param { number } channelId Active Chat Channel ID
|
||||
* @param { number } membershipId Requested user membership Id
|
||||
* @param { string } membershipStatus Requested user membership status
|
||||
*/
|
||||
export async function rejectChatChannelJoiningRequest(
|
||||
channelId,
|
||||
membershipId,
|
||||
membershipStatus,
|
||||
) {
|
||||
const response = await request(
|
||||
`/chat_channel_memberships/remove_membership`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: {
|
||||
status: membershipStatus || 'pending',
|
||||
chat_channel_id: channelId,
|
||||
membership_id: membershipId,
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
},
|
||||
);
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} channelId Active chat channel Id
|
||||
* @param {number} membershipId Chat channel joining request membership id
|
||||
*/
|
||||
export async function acceptChatChannelJoiningRequest(channelId, membershipId) {
|
||||
const response = await request(`/chat_channel_memberships/add_membership`, {
|
||||
method: 'POST',
|
||||
body: {
|
||||
chat_channel_id: channelId,
|
||||
membership_id: membershipId,
|
||||
chat_channel_membership: {
|
||||
user_action: 'accept',
|
||||
},
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
});
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export async function updateChatChannelDescription(
|
||||
channelId,
|
||||
description,
|
||||
discoverable,
|
||||
) {
|
||||
const response = await request(`/chat_channels/update_channel/${channelId}`, {
|
||||
method: 'PATCH',
|
||||
body: { chat_channel: { description, discoverable } },
|
||||
credentials: 'same-origin',
|
||||
});
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Active chat channel invitation
|
||||
*
|
||||
* @param {numner} channelId Active chat channel
|
||||
* @param {string} invitationUsernames UserNames coma seprated
|
||||
*/
|
||||
export async function sendChatChannelInvitation(
|
||||
channelId,
|
||||
invitationUsernames,
|
||||
) {
|
||||
const response = await request(
|
||||
`/chat_channel_memberships/create_membership_request`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: {
|
||||
chat_channel_membership: {
|
||||
chat_channel_id: channelId,
|
||||
invitation_usernames: invitationUsernames,
|
||||
},
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
},
|
||||
);
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is used to leave the chat channel.
|
||||
*
|
||||
* @param {number} membershipId Current User Chat channel membership id
|
||||
*/
|
||||
export async function leaveChatChannelMembership(membershipId) {
|
||||
const response = await request(
|
||||
`/chat_channel_memberships/leave_membership/${membershipId}`,
|
||||
{
|
||||
method: 'PATCH',
|
||||
|
||||
credentials: 'same-origin',
|
||||
},
|
||||
);
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
|
@ -1,21 +1,18 @@
|
|||
import { request } from '../../utilities/http';
|
||||
|
||||
export function rejectJoiningRequest(
|
||||
channelId,
|
||||
membershipId,
|
||||
successCb,
|
||||
failureCb,
|
||||
) {
|
||||
fetch(`/chat_channel_memberships/remove_membership`, {
|
||||
request(`/chat_channel_memberships/remove_membership`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'X-CSRF-Token': window.csrfToken,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
body: {
|
||||
status: 'pending',
|
||||
chat_channel_id: channelId,
|
||||
membership_id: membershipId,
|
||||
}),
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
})
|
||||
.then((response) => response.json())
|
||||
|
|
@ -29,20 +26,15 @@ export function acceptJoiningRequest(
|
|||
successCb,
|
||||
failureCb,
|
||||
) {
|
||||
fetch(`/chat_channel_memberships/add_membership`, {
|
||||
request(`/chat_channel_memberships/add_membership`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'X-CSRF-Token': window.csrfToken,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
body: {
|
||||
chat_channel_id: channelId,
|
||||
membership_id: membershipId,
|
||||
chat_channel_membership: {
|
||||
user_action: 'accept',
|
||||
},
|
||||
}),
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
})
|
||||
.then((response) => response.json())
|
||||
|
|
@ -51,18 +43,13 @@ export function acceptJoiningRequest(
|
|||
}
|
||||
|
||||
export function sendChannelRequest(id, successCb, failureCb) {
|
||||
fetch(`/join_chat_channel`, {
|
||||
request(`/join_chat_channel`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'X-CSRF-Token': window.csrfToken,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
body: {
|
||||
chat_channel_membership: {
|
||||
chat_channel_id: id,
|
||||
},
|
||||
}),
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
})
|
||||
.then((response) => response.json())
|
||||
|
|
|
|||
|
|
@ -867,6 +867,7 @@ export default class Chat extends Component {
|
|||
e.stopPropagation();
|
||||
|
||||
const { activeChannelId, activeChannel } = this.state;
|
||||
|
||||
if (content.startsWith('chat_channels/')) {
|
||||
this.setActiveContentState(activeChannelId, {
|
||||
type_of: 'loading-user',
|
||||
|
|
@ -936,9 +937,14 @@ export default class Chat extends Component {
|
|||
fullscreenContent: mode,
|
||||
expanded: mode === null || window.innerWidth > 1600,
|
||||
});
|
||||
} else if (target.dataset.content === 'chat_channel_setting') {
|
||||
this.setActiveContent({
|
||||
data: {},
|
||||
type_of: 'chat-channel-setting',
|
||||
activeMembershipId: activeChannel.id
|
||||
});
|
||||
}
|
||||
}
|
||||
document.getElementById('messageform').focus();
|
||||
return false;
|
||||
};
|
||||
|
||||
|
|
@ -1050,7 +1056,7 @@ export default class Chat extends Component {
|
|||
<b>must</b>
|
||||
</em>
|
||||
{' '}
|
||||
abide by the
|
||||
abide by the
|
||||
{' '}
|
||||
<a href="/code-of-conduct">code of conduct</a>
|
||||
.
|
||||
|
|
@ -1062,7 +1068,7 @@ export default class Chat extends Component {
|
|||
return (
|
||||
<div className="chatmessage" style={{ color: 'grey' }}>
|
||||
<div className="chatmessage__body">
|
||||
You have joined
|
||||
You have joined
|
||||
{' '}
|
||||
{activeChannel.channel_name}
|
||||
! All interactions
|
||||
|
|
@ -1071,7 +1077,7 @@ export default class Chat extends Component {
|
|||
<b>must</b>
|
||||
</em>
|
||||
{' '}
|
||||
abide by the
|
||||
abide by the
|
||||
{' '}
|
||||
<a href="/code-of-conduct">code of conduct</a>
|
||||
.
|
||||
|
|
@ -1717,6 +1723,7 @@ export default class Chat extends Component {
|
|||
this.toggleSearchShowing();
|
||||
};
|
||||
|
||||
|
||||
renderChannelHeaderInner = () => {
|
||||
const { activeChannel } = this.state;
|
||||
if (activeChannel.channel_type === 'direct') {
|
||||
|
|
@ -1732,9 +1739,9 @@ export default class Chat extends Component {
|
|||
}
|
||||
return (
|
||||
<a
|
||||
href={`/chat_channel_memberships/${activeChannel.id}/edit`}
|
||||
href='#/'
|
||||
onClick={this.triggerActiveContent}
|
||||
data-content="sidecar-chat_channel_membership"
|
||||
data-content="chat_channel_setting"
|
||||
>
|
||||
{activeChannel.channel_name}
|
||||
</a>
|
||||
|
|
@ -1753,12 +1760,8 @@ export default class Chat extends Component {
|
|||
const dataContent =
|
||||
activeChannel.channel_type === 'direct'
|
||||
? 'sidecar-user'
|
||||
: `sidecar-chat_channel_membership`;
|
||||
: `chat_channel_setting`;
|
||||
|
||||
const path =
|
||||
activeChannel.channel_type === 'direct'
|
||||
? `/${activeChannel.channel_username}`
|
||||
: `/chat_channel_memberships/${activeChannel.id}/edit`;
|
||||
|
||||
return (
|
||||
<a
|
||||
|
|
@ -1768,7 +1771,7 @@ export default class Chat extends Component {
|
|||
if (e.keyCode === 13) this.triggerActiveContent(e);
|
||||
}}
|
||||
tabIndex="0"
|
||||
href={path}
|
||||
href='#/'
|
||||
data-content={dataContent}
|
||||
>
|
||||
<img
|
||||
|
|
|
|||
|
|
@ -3,13 +3,14 @@ import PropTypes from 'prop-types';
|
|||
import Article from './article';
|
||||
import ChannelRequest from './channelRequest';
|
||||
import RequestManager from './requestManager';
|
||||
import ChatChannelSettings from './ChatChannelSettings/ChatChannelSettings';
|
||||
|
||||
export default class Content extends Component {
|
||||
static propTypes = {
|
||||
resource: PropTypes.object,
|
||||
activeChannelId: PropTypes.number,
|
||||
pusherKey: PropTypes.string,
|
||||
fullscreen: PropTypes.bool,
|
||||
resource : PropTypes.object,
|
||||
activeChannelId : PropTypes.number,
|
||||
pusherKey : PropTypes.string,
|
||||
fullscreen : PropTypes.bool
|
||||
};
|
||||
|
||||
render() {
|
||||
|
|
@ -17,14 +18,9 @@ export default class Content extends Component {
|
|||
if (!this.props.resource) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const smartSvgIcon = (content, d) => (
|
||||
<svg
|
||||
data-content={content}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
height="24"
|
||||
>
|
||||
<svg data-content={content} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||
<path data-content={content} fill="none" d="M0 0h24v24H0z" />
|
||||
<path data-content={content} d={d} />
|
||||
</svg>
|
||||
|
|
@ -42,7 +38,7 @@ export default class Content extends Component {
|
|||
>
|
||||
{smartSvgIcon(
|
||||
'exit',
|
||||
'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',
|
||||
'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'
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
|
|
@ -50,16 +46,18 @@ export default class Content extends Component {
|
|||
data-content="fullscreen"
|
||||
style={{ left: '39px' }}
|
||||
>
|
||||
{' '}
|
||||
{fullscreen
|
||||
? smartSvgIcon(
|
||||
'fullscreen',
|
||||
'M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z',
|
||||
)
|
||||
: smartSvgIcon(
|
||||
'fullscreen',
|
||||
'M20 3h2v6h-2V5h-4V3h4zM4 3h4v2H4v4H2V3h2zm16 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z',
|
||||
)}
|
||||
{' '}
|
||||
{fullscreen ? (
|
||||
smartSvgIcon(
|
||||
'fullscreen',
|
||||
'M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z'
|
||||
)
|
||||
) : (
|
||||
smartSvgIcon(
|
||||
'fullscreen',
|
||||
'M20 3h2v6h-2V5h-4V3h4zM4 3h4v2H4v4H2V3h2zm16 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z'
|
||||
)
|
||||
)}
|
||||
</button>
|
||||
{display(this.props)}
|
||||
</div>
|
||||
|
|
@ -69,27 +67,27 @@ export default class Content extends Component {
|
|||
|
||||
function display(props) {
|
||||
const { resource } = props;
|
||||
if (resource.type_of === 'loading-user') {
|
||||
return <div className="loading-user" />;
|
||||
}
|
||||
if (resource.type_of === 'article') {
|
||||
return <Article resource={resource} />;
|
||||
}
|
||||
if (resource.type_of === 'channel-request') {
|
||||
return (
|
||||
<ChannelRequest
|
||||
resource={resource.data}
|
||||
handleJoiningRequest={resource.handleJoiningRequest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (resource.type_of === 'channel-request-manager') {
|
||||
return (
|
||||
<RequestManager
|
||||
resource={resource.data}
|
||||
handleRequestRejection={resource.handleRequestRejection}
|
||||
handleRequestApproval={resource.handleRequestApproval}
|
||||
/>
|
||||
);
|
||||
|
||||
switch(resource.type_of) {
|
||||
case 'loading-user':
|
||||
return <div className="loading-user" />
|
||||
case 'article':
|
||||
return <Article resource={resource} />;
|
||||
case 'channel-request':
|
||||
return <ChannelRequest resource={resource.data} handleJoiningRequest={resource.handleJoiningRequest} />;
|
||||
case 'channel-request-manager':
|
||||
return (
|
||||
<RequestManager
|
||||
resource={resource.data}
|
||||
handleRequestRejection={resource.handleRequestRejection}
|
||||
handleRequestApproval={resource.handleRequestApproval}
|
||||
/>
|
||||
)
|
||||
case 'chat-channel-setting':
|
||||
return (
|
||||
<ChatChannelSettings resource={resource.data} activeMembershipId={resource.activeMembershipId} />
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,4 +10,20 @@ class ChatChannelMembershipPolicy < ApplicationPolicy
|
|||
def destroy?
|
||||
record.present? && user.id == record.user_id
|
||||
end
|
||||
|
||||
def leave_membership?
|
||||
record.present? && user.id == record.user_id
|
||||
end
|
||||
|
||||
def update_membership?
|
||||
record.present? && user.id == record.user_id
|
||||
end
|
||||
|
||||
def invitation?
|
||||
record.present? && user.id == record.user_id
|
||||
end
|
||||
|
||||
def chat_channel_info?
|
||||
record.present? && user.id == record.user_id
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -35,6 +35,10 @@ class ChatChannelPolicy < ApplicationPolicy
|
|||
user_part_of_channel && channel_is_direct
|
||||
end
|
||||
|
||||
def update_channel?
|
||||
user_can_edit_channel
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def user_can_edit_channel
|
||||
|
|
|
|||
42
app/presenters/chat_channel_detail_presenter.rb
Normal file
42
app/presenters/chat_channel_detail_presenter.rb
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
class ChatChannelDetailPresenter
|
||||
def initialize(chat_channel, current_membership)
|
||||
@chat_channel = chat_channel
|
||||
@current_membership = current_membership
|
||||
end
|
||||
|
||||
attr_accessor :chat_channel, :current_membership
|
||||
|
||||
def as_json
|
||||
{
|
||||
chat_channel: {
|
||||
name: chat_channel.channel_name,
|
||||
type: chat_channel.channel_type,
|
||||
description: chat_channel.description,
|
||||
discoverable: chat_channel.discoverable,
|
||||
slug: chat_channel.slug,
|
||||
status: chat_channel.status,
|
||||
id: chat_channel.id
|
||||
},
|
||||
memberships: {
|
||||
active: membership_users(chat_channel.active_memberships),
|
||||
pending: membership_users(chat_channel.pending_memberships),
|
||||
requested: membership_users(chat_channel.requested_memberships)
|
||||
},
|
||||
current_membership: current_membership
|
||||
}
|
||||
end
|
||||
|
||||
def membership_users(memberships)
|
||||
memberships.includes(:user).map do |membership|
|
||||
{
|
||||
name: membership.user.name,
|
||||
username: membership.user.username,
|
||||
user_id: membership.user.id,
|
||||
membership_id: membership.id,
|
||||
role: membership.role,
|
||||
status: membership.status,
|
||||
image: ProfileImage.new(membership.user).get(width: 90)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
19
app/presenters/membership_user_presenter.rb
Normal file
19
app/presenters/membership_user_presenter.rb
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
class MembershipUserPresenter
|
||||
def initialize(membership)
|
||||
@membership = membership
|
||||
end
|
||||
|
||||
attr_accessor :membership
|
||||
|
||||
def as_json
|
||||
{
|
||||
name: membership.user.name,
|
||||
username: membership.user.username,
|
||||
user_id: membership.user.id,
|
||||
membership_id: membership.id,
|
||||
role: membership.role,
|
||||
status: membership.status,
|
||||
image: ProfileImage.new(membership.user).get(width: 90)
|
||||
}
|
||||
end
|
||||
end
|
||||
5
app/services/application_service.rb
Normal file
5
app/services/application_service.rb
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
class ApplicationService
|
||||
def self.perform(*args, &block)
|
||||
new(*args, &block).perform
|
||||
end
|
||||
end
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
class ChatChannelUpdateService
|
||||
class ChatChannelUpdateService < ApplicationService
|
||||
attr_accessor :chat_channel, :params
|
||||
|
||||
def initialize(chat_channel, params)
|
||||
|
|
@ -6,7 +6,8 @@ class ChatChannelUpdateService
|
|||
@params = params
|
||||
end
|
||||
|
||||
def update
|
||||
def perform
|
||||
chat_channel.update(params)
|
||||
chat_channel
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ module PracticalDeveloper
|
|||
config.autoload_paths += Dir["#{config.root}/app/labor/"]
|
||||
config.autoload_paths += Dir["#{config.root}/app/decorators/"]
|
||||
config.autoload_paths += Dir["#{config.root}/app/services/"]
|
||||
config.autoload_paths += Dir["#{config.root}/app/presenters/"]
|
||||
config.autoload_paths += Dir["#{config.root}/app/liquid_tags/"]
|
||||
config.autoload_paths += Dir["#{config.root}/app/black_box/"]
|
||||
config.autoload_paths += Dir["#{config.root}/app/sanitizers"]
|
||||
|
|
|
|||
|
|
@ -283,6 +283,15 @@ Rails.application.routes.draw do
|
|||
|
||||
post "/pusher/auth" => "pusher#auth"
|
||||
|
||||
# Chat channel
|
||||
patch "/chat_channels/update_channel/:id" => "chat_channels#update_channel"
|
||||
|
||||
# Chat Channel Membership json response
|
||||
get "/chat_channel_memberships/chat_channel_info/:id" => "chat_channel_memberships#chat_channel_info"
|
||||
post "/chat_channel_memberships/create_membership_request" => "chat_channel_memberships#create_membership_request"
|
||||
patch "/chat_channel_memberships/leave_membership/:id" => "chat_channel_memberships#leave_membership"
|
||||
patch "/chat_channel_memberships/update_membership/:id" => "chat_channel_memberships#update_membership"
|
||||
|
||||
get "/social_previews/article/:id" => "social_previews#article", :as => :article_social_preview
|
||||
get "/social_previews/user/:id" => "social_previews#user", :as => :user_social_preview
|
||||
get "/social_previews/organization/:id" => "social_previews#organization", :as => :organization_social_preview
|
||||
|
|
|
|||
|
|
@ -10,178 +10,121 @@ RSpec.describe "ChatChannelMemberships", type: :request do
|
|||
chat_channel.add_users([user])
|
||||
end
|
||||
|
||||
describe "GET /chat_channel_memberships" do
|
||||
context "when pending invitations exists" do
|
||||
describe "GET /chat_channel_info" do
|
||||
context "when chat channel membership role is mod" do
|
||||
before do
|
||||
user.add_role(:super_admin)
|
||||
post "/chat_channel_memberships", params: {
|
||||
chat_channel_membership: {
|
||||
invitation_usernames: second_user.username.to_s,
|
||||
chat_channel_id: chat_channel.id
|
||||
}
|
||||
}
|
||||
|
||||
membership = ChatChannelMembership.find_by(chat_channel_id: chat_channel.id, user_id: user.id)
|
||||
get "/chat_channel_memberships/chat_channel_info/#{membership.id}"
|
||||
end
|
||||
|
||||
it "shows chat_channel_memberships list pending invitation" do
|
||||
sign_in second_user
|
||||
get "/chat_channel_memberships"
|
||||
expect(response.body).to include "Pending Invitations"
|
||||
expect(response.body).to include chat_channel.channel_name.to_s
|
||||
it "return all details of chat channel" do
|
||||
expect(response.status).to eq(200)
|
||||
puts response.body.inspect
|
||||
expect(JSON.parse(response.body)["result"].keys).to eq(%w[chat_channel memberships current_membership])
|
||||
end
|
||||
end
|
||||
|
||||
context "when no pending invitation" do
|
||||
it "shows chat_channel_memberships list pending invitation" do
|
||||
sign_in second_user
|
||||
get "/chat_channel_memberships"
|
||||
expect(response.body).to include "You have no pending invitations"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /chat_channel_memberships/find_by_chat_channel_id" do
|
||||
context "when user is logged in" do
|
||||
context "when membership role is member" do
|
||||
before do
|
||||
sign_in second_user
|
||||
chat_channel.add_users([second_user])
|
||||
|
||||
membership = ChatChannelMembership.find_by(chat_channel_id: chat_channel.id, user_id: second_user.id)
|
||||
get "/chat_channel_memberships/chat_channel_info/#{membership.id}"
|
||||
end
|
||||
|
||||
it "returns chat channel membership details" do
|
||||
sign_in second_user
|
||||
get "/chat_channel_memberships/find_by_chat_channel_id", params: { chat_channel_id: chat_channel.id }
|
||||
expected_keys = %w[id status chat_channel_id last_opened_at channel_text
|
||||
channel_last_message_at channel_status channel_username
|
||||
channel_type channel_name channel_image
|
||||
channel_modified_slug channel_messages_count]
|
||||
expect(response.parsed_body.keys).to(match_array(expected_keys))
|
||||
end
|
||||
end
|
||||
|
||||
context "when user is not logged in" do
|
||||
it "renders not_found" do
|
||||
expect do
|
||||
get "/chat_channel_memberships/find_by_chat_channel_id", params: {}
|
||||
end.to raise_error(ActiveRecord::RecordNotFound)
|
||||
it "return only channel info and current membership" do
|
||||
expect(response.status).to eq(200)
|
||||
expect(JSON.parse(response.body)["result"].keys).to eq(%w[chat_channel memberships current_membership])
|
||||
expect(JSON.parse(response.body)["result"]["memberships"]["pending"].length).to eq(0)
|
||||
expect(JSON.parse(response.body)["result"]["memberships"]["requested"].length).to eq(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /chat_channel_memberships/:id/edit" do
|
||||
before do
|
||||
chat_channel.add_users([second_user])
|
||||
end
|
||||
|
||||
let(:chat_channel_membership) { chat_channel.chat_channel_memberships.where(user_id: second_user.id).first }
|
||||
|
||||
context "when user is not logged in" do
|
||||
it "raise Pundit::NotAuthorizedError" do
|
||||
expect do
|
||||
get "/chat_channel_memberships/#{chat_channel_membership.id}/edit"
|
||||
end.to raise_error(Pundit::NotAuthorizedError)
|
||||
end
|
||||
end
|
||||
|
||||
context "when user is logged in and channel id is wrong" do
|
||||
it "raise ActiveRecord::RecordNotFound" do
|
||||
sign_in second_user
|
||||
expect do
|
||||
get "/chat_channel_memberships/ERW/edit"
|
||||
end.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
end
|
||||
|
||||
context "when user is channel member" do
|
||||
it "allows user to view channel members" do
|
||||
sign_in second_user
|
||||
get "/chat_channel_memberships/#{chat_channel_membership.id}/edit"
|
||||
expect(response.body).to include("Members")
|
||||
expect(response.body).to include(user.username.to_s)
|
||||
expect(response.body).to include(second_user.username.to_s)
|
||||
expect(response.body).not_to include("Pending Invitations")
|
||||
end
|
||||
end
|
||||
|
||||
context "when user is channel moderator" do
|
||||
it "allows user to view channel members" do
|
||||
sign_in second_user
|
||||
chat_channel_membership.update(role: "mod")
|
||||
get "/chat_channel_memberships/#{chat_channel_membership.id}/edit"
|
||||
expect(response.body).to include("Members")
|
||||
expect(response.body).to include(user.username.to_s)
|
||||
expect(response.body).to include(second_user.username.to_s)
|
||||
expect(response.body).to include("Pending Invitations")
|
||||
expect(response.body).to include("You are a channel mod")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST /chat_channel_memberships" do
|
||||
context "when user is super admin" do
|
||||
it "creates chat channel invitation" do
|
||||
describe "POST/ Send Invitation fot chat channel" do
|
||||
context "when chat channel membership role is mod" do
|
||||
before do
|
||||
user.add_role(:super_admin)
|
||||
expect do
|
||||
post "/chat_channel_memberships", params: {
|
||||
chat_channel_membership: {
|
||||
invitation_usernames: second_user.username.to_s,
|
||||
chat_channel_id: chat_channel.id
|
||||
}
|
||||
}
|
||||
end.to change { ChatChannelMembership.all.size }.by(1)
|
||||
expect(ChatChannelMembership.last.status).to eq("pending")
|
||||
end
|
||||
end
|
||||
|
||||
context "when user is channel moderator, and invited user was a member of channel, and than left channel" do
|
||||
it "creates chat channel invitation" do
|
||||
chat_channel.chat_channel_memberships.where(user_id: user.id).update(role: "mod")
|
||||
ChatChannelMembership.create(chat_channel_id: chat_channel.id, user_id: second_user.id, status: "left_channel")
|
||||
post "/chat_channel_memberships", params: {
|
||||
post "/chat_channel_memberships/create_membership_request", params: {
|
||||
chat_channel_membership: {
|
||||
invitation_usernames: second_user.username.to_s,
|
||||
chat_channel_id: chat_channel.id
|
||||
}
|
||||
}
|
||||
expect(ChatChannelMembership.last.status).to eq("pending")
|
||||
end
|
||||
|
||||
it "Send invitation to user" do
|
||||
expect(response.status).to eq(200)
|
||||
end
|
||||
end
|
||||
|
||||
context "when user is channel moderator, invited user was not a member of channel" do
|
||||
it "creates chat channel invitation" do
|
||||
chat_channel.chat_channel_memberships.where(user_id: user.id).update(role: "mod")
|
||||
chat_channel_members_count = ChatChannelMembership.all.size
|
||||
post "/chat_channel_memberships", params: {
|
||||
context "when chat channel membership role is member" do
|
||||
before do
|
||||
sign_in second_user
|
||||
chat_channel.add_users([second_user])
|
||||
|
||||
post "/chat_channel_memberships/create_membership_request", params: {
|
||||
chat_channel_membership: {
|
||||
invitation_usernames: second_user.username.to_s,
|
||||
invitation_usernames: "test2031",
|
||||
chat_channel_id: chat_channel.id
|
||||
}
|
||||
}
|
||||
expect(ChatChannelMembership.all.size).to eq(chat_channel_members_count + 1)
|
||||
expect(ChatChannelMembership.last.status).to eq("pending")
|
||||
end
|
||||
|
||||
it "disallows invitation creation when org private group" do
|
||||
chat_channel.update_column(:channel_name, "@org private group chat")
|
||||
chat_channel.chat_channel_memberships.where(user_id: user.id).update(role: "mod")
|
||||
expect do
|
||||
post "/chat_channel_memberships", params: {
|
||||
chat_channel_membership: {
|
||||
invitation_usernames: second_user.username.to_s,
|
||||
chat_channel_id: chat_channel.id
|
||||
}
|
||||
}
|
||||
end.to raise_error(Pundit::NotAuthorizedError)
|
||||
it "user not authorized" do
|
||||
expect(response.status).to eq(401)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST/ remove_membership" do
|
||||
context "when the user is super admin" do
|
||||
it "remove the user from chat channel" do
|
||||
allow(Pusher).to receive(:trigger).and_return(true)
|
||||
|
||||
user.add_role(:super_admin)
|
||||
membership = ChatChannelMembership.find_by(chat_channel_id: chat_channel.id, user_id: user.id)
|
||||
|
||||
post "/chat_channel_memberships/remove_membership", params: {
|
||||
chat_channel_id: chat_channel.id,
|
||||
membership_id: membership.id
|
||||
}
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(membership.reload.status).to eq("removed_from_channel")
|
||||
end
|
||||
end
|
||||
|
||||
context "when user is not authorized to add channel membership" do
|
||||
it "raise Pundit::NotAuthorizedError" do
|
||||
expect do
|
||||
post "/chat_channel_memberships", params: {
|
||||
chat_channel_membership: {
|
||||
invitation_usernames: second_user.username.to_s,
|
||||
chat_channel_id: chat_channel.id
|
||||
}
|
||||
}
|
||||
end.to raise_error(Pundit::NotAuthorizedError)
|
||||
context "when user is chat channel membership role is mod" do
|
||||
it "remove the user from chat channel" do
|
||||
allow(Pusher).to receive(:trigger).and_return(true)
|
||||
membership = chat_channel.chat_channel_memberships.find_by(user_id: user.id)
|
||||
membership.update(role: "mod")
|
||||
|
||||
post "/chat_channel_memberships/remove_membership", params: {
|
||||
chat_channel_id: chat_channel.id,
|
||||
membership_id: membership.id
|
||||
}
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(membership.reload.status).to eq("removed_from_channel")
|
||||
end
|
||||
end
|
||||
|
||||
context "when user chat channel membership role is member" do
|
||||
it "user is not unauthorized" do
|
||||
sign_in second_user
|
||||
membership = ChatChannelMembership.last
|
||||
post "/chat_channel_memberships/remove_membership", params: {
|
||||
chat_channel_id: chat_channel.id,
|
||||
membership_id: membership.id
|
||||
}
|
||||
|
||||
expect(response.status).to eq(401)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -189,7 +132,7 @@ RSpec.describe "ChatChannelMemberships", type: :request do
|
|||
describe "PUT /chat_channel_memberships/:id" do
|
||||
before do
|
||||
user.add_role(:super_admin)
|
||||
post "/chat_channel_memberships", params: {
|
||||
post "/chat_channel_memberships/create_membership_request", params: {
|
||||
chat_channel_membership: {
|
||||
invitation_usernames: second_user.username.to_s,
|
||||
chat_channel_id: chat_channel.id
|
||||
|
|
@ -227,97 +170,96 @@ RSpec.describe "ChatChannelMemberships", type: :request do
|
|||
end
|
||||
|
||||
context "when user not logged in" do
|
||||
it "raise Pundit::NotAuthorizedError" do
|
||||
it "unauthorized" do
|
||||
membership = ChatChannelMembership.last
|
||||
expect do
|
||||
put "/chat_channel_memberships/#{membership.id}", params: {
|
||||
chat_channel_membership: { user_action: "accept" }
|
||||
}
|
||||
end.to raise_error(Pundit::NotAuthorizedError)
|
||||
end
|
||||
end
|
||||
put "/chat_channel_memberships/#{membership.id}", params: {
|
||||
chat_channel_membership: { user_action: "accept" }
|
||||
}
|
||||
|
||||
context "when user is unauthorized" do
|
||||
it "raise Pundit::NotAuthorizedError" do
|
||||
membership = ChatChannelMembership.last
|
||||
sign_in user
|
||||
expect do
|
||||
put "/chat_channel_memberships/#{membership.id}", params: {
|
||||
chat_channel_membership: { user_action: "accept" }
|
||||
}
|
||||
end.to raise_error(Pundit::NotAuthorizedError)
|
||||
expect(response.status).to eq(401)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "DELETE /chat_channel_memberships/:id" do
|
||||
describe "PATCH/ update_membership" do
|
||||
context "when user is logged in" do
|
||||
it "update the notification status" do
|
||||
membership = ChatChannelMembership.find_by(chat_channel_id: chat_channel.id, user_id: user.id)
|
||||
membership.update(show_global_badge_notification: false)
|
||||
patch "/chat_channel_memberships/update_membership/#{membership.id}", params: {
|
||||
chat_channel_membership: {
|
||||
show_global_badge_notification: true
|
||||
}
|
||||
}
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(membership.reload.show_global_badge_notification).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context "when user is not logged in" do
|
||||
it "not found membership" do
|
||||
patch "/chat_channel_memberships/update_membership/", params: {
|
||||
chat_channel_membership: {
|
||||
show_global_badge_notification: true
|
||||
}
|
||||
}
|
||||
|
||||
expect(response.status).to eq(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST/ /leave_membership/:id" do
|
||||
context "when user is logged in" do
|
||||
it "leaves chat channel" do
|
||||
allow(Pusher).to receive(:trigger).and_return(true)
|
||||
chat_channel.add_users([second_user])
|
||||
membership = ChatChannelMembership.last
|
||||
|
||||
sign_in second_user
|
||||
delete "/chat_channel_memberships/#{membership.id}", params: {}
|
||||
expect(ChatChannelMembership.find(membership.id).status).to eq("left_channel")
|
||||
expect(response).to(redirect_to(chat_channel_memberships_path))
|
||||
|
||||
patch "/chat_channel_memberships/leave_membership/#{membership.id}"
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(membership.reload.status).to eq("left_channel")
|
||||
end
|
||||
end
|
||||
|
||||
context "when user is not logged in" do
|
||||
it "raise Pundit::NotAuthorizedError" do
|
||||
it "unauthorized user" do
|
||||
chat_channel.add_users([second_user])
|
||||
membership = ChatChannelMembership.last
|
||||
expect do
|
||||
delete "/chat_channel_memberships/#{membership.id}", params: {}
|
||||
end.to(raise_error(Pundit::NotAuthorizedError))
|
||||
membership_status = membership.status
|
||||
patch "/chat_channel_memberships/leave_membership/#{membership.id}"
|
||||
|
||||
expect(response.status).to eq(401)
|
||||
expect(membership.reload.status).to eq(membership_status)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST /chat_channel_memberships/remove_membership" do
|
||||
before do
|
||||
chat_channel.add_users([second_user])
|
||||
end
|
||||
describe "GET /chat_channel_memberships/find_by_chat_channel_id" do
|
||||
context "when user is logged in" do
|
||||
before do
|
||||
chat_channel.add_users([second_user])
|
||||
end
|
||||
|
||||
context "when user is super admin" do
|
||||
it "removes member from channel" do
|
||||
allow(Pusher).to receive(:trigger).and_return(true)
|
||||
user.add_role(:super_admin)
|
||||
membership = chat_channel.chat_channel_memberships.where(user_id: user.id).last
|
||||
removed_channel_membership = ChatChannelMembership.last
|
||||
post "/chat_channel_memberships/remove_membership", params: {
|
||||
chat_channel_id: chat_channel.id,
|
||||
membership_id: removed_channel_membership.id
|
||||
}
|
||||
expect(removed_channel_membership.reload.status).to eq("removed_from_channel")
|
||||
expect(response).to(redirect_to(edit_chat_channel_membership_path(membership.id)))
|
||||
it "returns chat channel membership details" do
|
||||
sign_in second_user
|
||||
get "/chat_channel_memberships/find_by_chat_channel_id", params: { chat_channel_id: chat_channel.id }
|
||||
expected_keys = %w[id status chat_channel_id last_opened_at channel_text
|
||||
channel_last_message_at channel_status channel_username
|
||||
channel_type channel_name channel_image
|
||||
channel_modified_slug channel_messages_count]
|
||||
expect(response.parsed_body.keys).to(match_array(expected_keys))
|
||||
end
|
||||
end
|
||||
|
||||
context "when user is moderator of channel" do
|
||||
it "removes member from channel" do
|
||||
allow(Pusher).to receive(:trigger).and_return(true)
|
||||
membership = chat_channel.chat_channel_memberships.where(user_id: user.id).last
|
||||
membership.update(role: "mod")
|
||||
removed_channel_membership = ChatChannelMembership.last
|
||||
post "/chat_channel_memberships/remove_membership", params: {
|
||||
chat_channel_id: chat_channel.id,
|
||||
membership_id: removed_channel_membership.id
|
||||
}
|
||||
expect(removed_channel_membership.reload.status).to eq("removed_from_channel")
|
||||
expect(response).to(redirect_to(edit_chat_channel_membership_path(membership.id)))
|
||||
end
|
||||
end
|
||||
|
||||
context "when user is member of channel" do
|
||||
it "raise Pundit::NotAuthorizedError" do
|
||||
membership = ChatChannelMembership.last
|
||||
expect do
|
||||
post "/chat_channel_memberships/remove_membership", params: {
|
||||
chat_channel_id: chat_channel.id,
|
||||
membership_id: membership.id
|
||||
}
|
||||
end.to(raise_error(Pundit::NotAuthorizedError))
|
||||
context "when user is not logged in" do
|
||||
it "renders not_found" do
|
||||
get "/chat_channel_memberships/find_by_chat_channel_id", params: {}
|
||||
expect(response.status).to eq(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -338,24 +280,25 @@ RSpec.describe "ChatChannelMemberships", type: :request do
|
|||
}
|
||||
}
|
||||
expect(ChatChannelMembership.find(membership.id).status).to eq("active")
|
||||
expect(response).to(redirect_to(edit_chat_channel_membership_path(membership.id)))
|
||||
expect(response).to(redirect_to(chat_channel_memberships_path))
|
||||
end
|
||||
end
|
||||
|
||||
context "when user is member of channel" do
|
||||
it "raise Pundit::NotAuthorizedError" do
|
||||
expect do
|
||||
channel = ChatChannel.first
|
||||
membership = ChatChannelMembership.last
|
||||
membership.update(status: "joining_request")
|
||||
post "/chat_channel_memberships/add_membership", params: {
|
||||
membership_id: membership.id,
|
||||
chat_channel_id: channel.id,
|
||||
chat_channel_membership: {
|
||||
user_action: "accept"
|
||||
}
|
||||
it "User not authorize" do
|
||||
channel = ChatChannel.first
|
||||
membership = ChatChannelMembership.last
|
||||
membership.update(status: "joining_request")
|
||||
post "/chat_channel_memberships/add_membership", params: {
|
||||
membership_id: membership.id,
|
||||
chat_channel_id: channel.id,
|
||||
chat_channel_membership: {
|
||||
user_action: "accept"
|
||||
}
|
||||
end.to(raise_error(Pundit::NotAuthorizedError))
|
||||
}
|
||||
|
||||
expect(ChatChannelMembership.find(membership.id).status).to eq("joining_request")
|
||||
expect(response.status).to eq(401)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -176,6 +176,45 @@ RSpec.describe "ChatChannels", type: :request do
|
|||
end
|
||||
end
|
||||
|
||||
describe "PATCH /chat_channels/update_channel/:id" do
|
||||
it "updates chat channel for valid user" do
|
||||
user.add_role(:super_admin)
|
||||
membership = chat_channel.chat_channel_memberships.where(user_id: user.id).last
|
||||
membership.update(role: "mod")
|
||||
patch "/chat_channels/update_channel/#{chat_channel.id}", params: {
|
||||
chat_channel: {
|
||||
channel_name: "Hello Channel",
|
||||
slug: "hello-channelly",
|
||||
}
|
||||
}
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(ChatChannel.last.slug).to eq("hello-channelly")
|
||||
end
|
||||
|
||||
it "un-authorized users" do
|
||||
expect do
|
||||
patch "/chat_channels/update_channel/#{chat_channel.id}", params: {
|
||||
chat_channel: {
|
||||
channel_name: "Hello Channel",
|
||||
slug: "hello-channelly"
|
||||
}
|
||||
}
|
||||
end.to raise_error(Pundit::NotAuthorizedError)
|
||||
end
|
||||
|
||||
it "returns errors if channel is invalid" do
|
||||
# slug should be taken
|
||||
user.add_role(:super_admin)
|
||||
membership = chat_channel.chat_channel_memberships.where(user_id: user.id).last
|
||||
membership.update(role: "mod")
|
||||
patch "/chat_channels/update_channel/#{chat_channel.id}", params: {
|
||||
chat_channel: { channel_name:"Hello Channel", slug: invite_channel.slug }
|
||||
}
|
||||
expect(ChatChannel.last.slug).not_to eq("hello-channelly")
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST /chat_channels/:id/moderate" do
|
||||
it "raises NotAuthorizedError if user is not logged in" do
|
||||
expect do
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue