diff --git a/app/assets/stylesheets/chat.scss b/app/assets/stylesheets/chat.scss
index b103b6a84..38c5e03c3 100644
--- a/app/assets/stylesheets/chat.scss
+++ b/app/assets/stylesheets/chat.scss
@@ -1274,3 +1274,7 @@
.action button:nth-child(2n) {
margin-left: 10px;
}
+
+.channel_details {
+ margin-top: 30px;
+}
diff --git a/app/controllers/chat_channel_memberships_controller.rb b/app/controllers/chat_channel_memberships_controller.rb
index 7d5359ce2..f56627d58 100644
--- a/app/controllers/chat_channel_memberships_controller.rb
+++ b/app/controllers/chat_channel_memberships_controller.rb
@@ -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
diff --git a/app/controllers/chat_channels_controller.rb b/app/controllers/chat_channels_controller.rb
index 66e545f77..5fcddf69a 100644
--- a/app/controllers/chat_channels_controller.rb
+++ b/app/controllers/chat_channels_controller.rb
@@ -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)
diff --git a/app/javascript/chat/ChatChannelSettings/ActiveMembershipsSection.jsx b/app/javascript/chat/ChatChannelSettings/ActiveMembershipsSection.jsx
new file mode 100644
index 000000000..cb16fb577
--- /dev/null
+++ b/app/javascript/chat/ChatChannelSettings/ActiveMembershipsSection.jsx
@@ -0,0 +1,44 @@
+import { h } from 'preact';
+import PropTypes from 'prop-types';
+import Membership from './Membership';
+
+const ActiveMembershipSection = ({
+ activeMemberships,
+ removeMembership,
+ currentMembershipRole,
+}) => {
+ return (
+
+
Members
+ {activeMemberships && activeMemberships.length > 0
+ ? activeMemberships.map((pendingMembership) => (
+
+ ))
+ : null}
+
+ );
+};
+
+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;
diff --git a/app/javascript/chat/ChatChannelSettings/ChannelDescriptionSection.jsx b/app/javascript/chat/ChatChannelSettings/ChannelDescriptionSection.jsx
new file mode 100644
index 000000000..92684c303
--- /dev/null
+++ b/app/javascript/chat/ChatChannelSettings/ChannelDescriptionSection.jsx
@@ -0,0 +1,28 @@
+import { h } from 'preact';
+import PropTypes from 'prop-types';
+
+const ChannelDescriptionSection = ({
+ channelName,
+ channelDescription,
+ currentMembershipRole
+}) => {
+ return (
+
+
{channelName}
+
{channelDescription}
+
+ You are a channel
+ {' '}
+ {currentMembershipRole}
+
+
+ )
+}
+
+ChannelDescriptionSection.propTypes = {
+ channelName: PropTypes.string.isRequired,
+ currentMembershipRole: PropTypes.string.isRequired,
+ channelDescription: PropTypes.string.isRequired
+}
+
+export default ChannelDescriptionSection;
diff --git a/app/javascript/chat/ChatChannelSettings/ChatChannelMembershipSection.jsx b/app/javascript/chat/ChatChannelSettings/ChatChannelMembershipSection.jsx
new file mode 100644
index 000000000..45524e6d4
--- /dev/null
+++ b/app/javascript/chat/ChatChannelSettings/ChatChannelMembershipSection.jsx
@@ -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 (
+
+ )
+}
+
+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;
diff --git a/app/javascript/chat/ChatChannelSettings/ChatChannelSettings.jsx b/app/javascript/chat/ChatChannelSettings/ChatChannelSettings.jsx
new file mode 100644
index 000000000..75106cc2f
--- /dev/null
+++ b/app/javascript/chat/ChatChannelSettings/ChatChannelSettings.jsx
@@ -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(, 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 (
+
+ );
+ }
+}
diff --git a/app/javascript/chat/ChatChannelSettings/InviateForm.jsx b/app/javascript/chat/ChatChannelSettings/InviateForm.jsx
new file mode 100644
index 000000000..a7d746a37
--- /dev/null
+++ b/app/javascript/chat/ChatChannelSettings/InviateForm.jsx
@@ -0,0 +1,47 @@
+import { h } from 'preact';
+import PropTypes from 'prop-types';
+
+const InviteForm = ({
+ handleChatChannelInvitations,
+ invitationUsernames,
+ handleInvitationUsernames
+}) => {
+ return (
+
+
+
+
+
+
+
+
+
+ )
+}
+
+InviteForm.propTypes = {
+ handleInvitationUsernames: PropTypes.func.isRequired,
+ handleChatChannelInvitations: PropTypes.func.isRequired,
+ invitationUsernames: PropTypes.func.isRequired
+}
+
+export default InviteForm;
diff --git a/app/javascript/chat/ChatChannelSettings/LeaveMembershipSection.jsx b/app/javascript/chat/ChatChannelSettings/LeaveMembershipSection.jsx
new file mode 100644
index 000000000..f72e22fae
--- /dev/null
+++ b/app/javascript/chat/ChatChannelSettings/LeaveMembershipSection.jsx
@@ -0,0 +1,33 @@
+import { h } from 'preact';
+import PropsType from 'prop-types';
+
+const LeaveMembershipSection = ({
+ currentMembershipRole,
+ handleleaveChatChannelMembership,
+}) => {
+ if (currentMembershipRole !== 'member') {
+ return null;
+ }
+
+ return (
+
+
Danger Zone
+
+
+
+
+ );
+};
+
+LeaveMembershipSection.propTypes = {
+ currentMembershipRole: PropsType.string.isRequired,
+ handleleaveChatChannelMembership: PropsType.func.isRequired,
+};
+
+export default LeaveMembershipSection;
diff --git a/app/javascript/chat/ChatChannelSettings/Membership.jsx b/app/javascript/chat/ChatChannelSettings/Membership.jsx
new file mode 100644
index 000000000..6d321e7bb
--- /dev/null
+++ b/app/javascript/chat/ChatChannelSettings/Membership.jsx
@@ -0,0 +1,67 @@
+import { h } from 'preact';
+import PropTypes from 'prop-types';
+
+const Membership = ({
+ membership,
+ removeMembership,
+ membershipType,
+ chatChannelAcceptMembership,
+ currentMembershipRole,
+}) => {
+ return (
+
+
+
+
+
+ {membership.name}
+
+ {membershipType === 'requested' ? (
+
+ ) : null}
+ {membership.role !== 'mod' && currentMembershipRole === 'mod' ? (
+
+ ) : null}
+
+ );
+};
+
+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;
diff --git a/app/javascript/chat/ChatChannelSettings/ModFaqSection.jsx b/app/javascript/chat/ChatChannelSettings/ModFaqSection.jsx
new file mode 100644
index 000000000..2c2eface8
--- /dev/null
+++ b/app/javascript/chat/ChatChannelSettings/ModFaqSection.jsx
@@ -0,0 +1,30 @@
+import { h } from 'preact';
+import PropTypes from 'prop-types';
+
+const ModFaqSection = ({ currentMembershipRole }) => {
+ if (currentMembershipRole !== 'mod') {
+ return null;
+ }
+
+ return (
+
+
+ Questions about Connect Channel moderation? Contact
+
+ yo@dev.to
+
+
+
+ );
+};
+
+ModFaqSection.propTypes = {
+ currentMembershipRole: PropTypes.string.isRequired,
+};
+
+export default ModFaqSection;
diff --git a/app/javascript/chat/ChatChannelSettings/ModSection.jsx b/app/javascript/chat/ChatChannelSettings/ModSection.jsx
new file mode 100644
index 000000000..a45c06a60
--- /dev/null
+++ b/app/javascript/chat/ChatChannelSettings/ModSection.jsx
@@ -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 (
+
+
+
+
+ );
+};
+
+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;
diff --git a/app/javascript/chat/ChatChannelSettings/PendingMembershipSection.jsx b/app/javascript/chat/ChatChannelSettings/PendingMembershipSection.jsx
new file mode 100644
index 000000000..d3aa8b1af
--- /dev/null
+++ b/app/javascript/chat/ChatChannelSettings/PendingMembershipSection.jsx
@@ -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 (
+
+
Pending Invitations
+ {pendingMemberships && pendingMemberships.length > 0
+ ? pendingMemberships.map((pendingMembership) => (
+
+ ))
+ : null}
+
+ );
+};
+
+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;
diff --git a/app/javascript/chat/ChatChannelSettings/PersonalSetting.jsx b/app/javascript/chat/ChatChannelSettings/PersonalSetting.jsx
new file mode 100644
index 000000000..946d51c0c
--- /dev/null
+++ b/app/javascript/chat/ChatChannelSettings/PersonalSetting.jsx
@@ -0,0 +1,44 @@
+import { h } from 'preact';
+import PropTypes from 'prop-types';
+
+const PersonalSettings = ({
+ handlePersonChatChennelSetting,
+ showGlobalBadgeNotification,
+ updateCurrentMembershipNotificationSettings,
+}) => {
+ return (
+
+
Personal Settings
+
Notifications
+
+
+
+
+
+
+
+
+ );
+};
+
+PersonalSettings.propTypes = {
+ updateCurrentMembershipNotificationSettings: PropTypes.func.isRequired,
+ showGlobalBadgeNotification: PropTypes.bool.isRequired,
+ handlePersonChatChennelSetting: PropTypes.func.isRequired,
+};
+
+export default PersonalSettings;
diff --git a/app/javascript/chat/ChatChannelSettings/RequestedMembershipSection.jsx b/app/javascript/chat/ChatChannelSettings/RequestedMembershipSection.jsx
new file mode 100644
index 000000000..f060b0a63
--- /dev/null
+++ b/app/javascript/chat/ChatChannelSettings/RequestedMembershipSection.jsx
@@ -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 (
+
+
Joining Request
+ {requestedMemberships && requestedMemberships.length > 0
+ ? requestedMemberships.map((pendingMembership) => (
+
+ ))
+ : null}
+
+ );
+};
+
+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;
diff --git a/app/javascript/chat/ChatChannelSettings/SettingsForm.jsx b/app/javascript/chat/ChatChannelSettings/SettingsForm.jsx
new file mode 100644
index 000000000..37400ae9d
--- /dev/null
+++ b/app/javascript/chat/ChatChannelSettings/SettingsForm.jsx
@@ -0,0 +1,62 @@
+import { h } from 'preact';
+import PropTypes from 'prop-types';
+
+const SettingsFrom = ({
+ channelDescription,
+ handleDescriptionChange,
+ channelDiscoverable,
+ handleChannelDiscoverableStatus,
+ handleChannelDescriptionChanges,
+}) => {
+ return (
+
+
Channel Settings
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+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;
diff --git a/app/javascript/chat/__tests__/__snapshots__/activeMembershipsSection.test.jsx.snap b/app/javascript/chat/__tests__/__snapshots__/activeMembershipsSection.test.jsx.snap
new file mode 100644
index 000000000..fb0e0adf7
--- /dev/null
+++ b/app/javascript/chat/__tests__/__snapshots__/activeMembershipsSection.test.jsx.snap
@@ -0,0 +1,13 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[` should render and test snapshot 1`] = `
+
+
+ Members
+
+
+`;
diff --git a/app/javascript/chat/__tests__/__snapshots__/chatChannelDescription.test.jsx.snap b/app/javascript/chat/__tests__/__snapshots__/chatChannelDescription.test.jsx.snap
new file mode 100644
index 000000000..c9ee3ec39
--- /dev/null
+++ b/app/javascript/chat/__tests__/__snapshots__/chatChannelDescription.test.jsx.snap
@@ -0,0 +1,21 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[` should render and test snapshot 1`] = `
+
+
+ some name
+
+
+ some description
+
+
+ You are a channel mod
+
+
+`;
diff --git a/app/javascript/chat/__tests__/__snapshots__/chatChannelMembersection.test.jsx.snap b/app/javascript/chat/__tests__/__snapshots__/chatChannelMembersection.test.jsx.snap
new file mode 100644
index 000000000..01f2deba3
--- /dev/null
+++ b/app/javascript/chat/__tests__/__snapshots__/chatChannelMembersection.test.jsx.snap
@@ -0,0 +1,17 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[` should render and test snapshot 1`] = `
+
+`;
diff --git a/app/javascript/chat/__tests__/__snapshots__/chatChannelSettings.test.jsx.snap b/app/javascript/chat/__tests__/__snapshots__/chatChannelSettings.test.jsx.snap
new file mode 100644
index 000000000..f81483193
--- /dev/null
+++ b/app/javascript/chat/__tests__/__snapshots__/chatChannelSettings.test.jsx.snap
@@ -0,0 +1,3 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[` should render and test snapshot 1`] = `null`;
diff --git a/app/javascript/chat/__tests__/__snapshots__/inviteForm.test.jsx.snap b/app/javascript/chat/__tests__/__snapshots__/inviteForm.test.jsx.snap
new file mode 100644
index 000000000..f86a57ffa
--- /dev/null
+++ b/app/javascript/chat/__tests__/__snapshots__/inviteForm.test.jsx.snap
@@ -0,0 +1,34 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[` should render the test snapshot 1`] = `
+
+
+
+
+
+
+
+
+
+`;
diff --git a/app/javascript/chat/__tests__/__snapshots__/leaveMembershipSection.test.jsx.snap b/app/javascript/chat/__tests__/__snapshots__/leaveMembershipSection.test.jsx.snap
new file mode 100644
index 000000000..b474b380d
--- /dev/null
+++ b/app/javascript/chat/__tests__/__snapshots__/leaveMembershipSection.test.jsx.snap
@@ -0,0 +1,19 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[` should render and test snapshot 1`] = `
+
+
+ Danger Zone
+
+
+
+
+
+`;
diff --git a/app/javascript/chat/__tests__/__snapshots__/membership.test.jsx.snap b/app/javascript/chat/__tests__/__snapshots__/membership.test.jsx.snap
new file mode 100644
index 000000000..7290f242f
--- /dev/null
+++ b/app/javascript/chat/__tests__/__snapshots__/membership.test.jsx.snap
@@ -0,0 +1,27 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[` should render and test snapshot 1`] = `
+
+`;
diff --git a/app/javascript/chat/__tests__/__snapshots__/modFaqSection.test.jsx.snap b/app/javascript/chat/__tests__/__snapshots__/modFaqSection.test.jsx.snap
new file mode 100644
index 000000000..a468bf2b3
--- /dev/null
+++ b/app/javascript/chat/__tests__/__snapshots__/modFaqSection.test.jsx.snap
@@ -0,0 +1,21 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[` should render and test snapshot 1`] = `
+
+
+ Questions about Connect Channel moderation? Contact
+
+ yo@dev.to
+
+
+
+`;
diff --git a/app/javascript/chat/__tests__/__snapshots__/modSection.test.jsx.snap b/app/javascript/chat/__tests__/__snapshots__/modSection.test.jsx.snap
new file mode 100644
index 000000000..2471c44c1
--- /dev/null
+++ b/app/javascript/chat/__tests__/__snapshots__/modSection.test.jsx.snap
@@ -0,0 +1,82 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[` should render and test snapshot 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+ Channel Settings
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/app/javascript/chat/__tests__/__snapshots__/pendingMembershipsSection.test.jsx.snap b/app/javascript/chat/__tests__/__snapshots__/pendingMembershipsSection.test.jsx.snap
new file mode 100644
index 000000000..f2396b0f6
--- /dev/null
+++ b/app/javascript/chat/__tests__/__snapshots__/pendingMembershipsSection.test.jsx.snap
@@ -0,0 +1,13 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[` should render and test snapshot 1`] = `
+
+
+ Pending Invitations
+
+
+`;
diff --git a/app/javascript/chat/__tests__/__snapshots__/personalSetting.test.jsx.snap b/app/javascript/chat/__tests__/__snapshots__/personalSetting.test.jsx.snap
new file mode 100644
index 000000000..fb0989775
--- /dev/null
+++ b/app/javascript/chat/__tests__/__snapshots__/personalSetting.test.jsx.snap
@@ -0,0 +1,38 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[` should render and test snapshot 1`] = `
+
+
+ Personal Settings
+
+
+ Notifications
+
+
+
+
+
+
+
+
+
+`;
diff --git a/app/javascript/chat/__tests__/__snapshots__/requestedMembershipSection.test.jsx.snap b/app/javascript/chat/__tests__/__snapshots__/requestedMembershipSection.test.jsx.snap
new file mode 100644
index 000000000..1e5a6c6d7
--- /dev/null
+++ b/app/javascript/chat/__tests__/__snapshots__/requestedMembershipSection.test.jsx.snap
@@ -0,0 +1,13 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[` should render and test snapshot 1`] = `
+
+
+ Joining Request
+
+
+`;
diff --git a/app/javascript/chat/__tests__/__snapshots__/settingsForm.test.jsx.snap b/app/javascript/chat/__tests__/__snapshots__/settingsForm.test.jsx.snap
new file mode 100644
index 000000000..728645c99
--- /dev/null
+++ b/app/javascript/chat/__tests__/__snapshots__/settingsForm.test.jsx.snap
@@ -0,0 +1,49 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[` should render and test snapshot 1`] = `
+
+
+ Channel Settings
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/app/javascript/chat/__tests__/activeMembershipsSection.test.jsx b/app/javascript/chat/__tests__/activeMembershipsSection.test.jsx
new file mode 100644
index 000000000..f6edee9b1
--- /dev/null
+++ b/app/javascript/chat/__tests__/activeMembershipsSection.test.jsx
@@ -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) => (
+
+);
+
+describe('', () => {
+ 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);
+ });
+});
diff --git a/app/javascript/chat/__tests__/chatChannelDescription.test.jsx b/app/javascript/chat/__tests__/chatChannelDescription.test.jsx
new file mode 100644
index 000000000..9995352da
--- /dev/null
+++ b/app/javascript/chat/__tests__/chatChannelDescription.test.jsx
@@ -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 (
+
+ )
+}
+
+describe('', () => {
+ 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)
+ })
+})
diff --git a/app/javascript/chat/__tests__/chatChannelMembersection.test.jsx b/app/javascript/chat/__tests__/chatChannelMembersection.test.jsx
new file mode 100644
index 000000000..2682dd474
--- /dev/null
+++ b/app/javascript/chat/__tests__/chatChannelMembersection.test.jsx
@@ -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 (
+
+ )
+}
+
+describe('', () => {
+ 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)
+ })
+})
\ No newline at end of file
diff --git a/app/javascript/chat/__tests__/chatChannelSettingActions.test.js b/app/javascript/chat/__tests__/chatChannelSettingActions.test.js
new file mode 100644
index 000000000..b1a4ff31e
--- /dev/null
+++ b/app/javascript/chat/__tests__/chatChannelSettingActions.test.js
@@ -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);
+ });
+ });
+});
diff --git a/app/javascript/chat/__tests__/chatChannelSettings.test.jsx b/app/javascript/chat/__tests__/chatChannelSettings.test.jsx
new file mode 100644
index 000000000..3ea12c753
--- /dev/null
+++ b/app/javascript/chat/__tests__/chatChannelSettings.test.jsx
@@ -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) => (
+
+);
+
+describe('', () => {
+ 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);
+ });
+});
diff --git a/app/javascript/chat/__tests__/inviteForm.test.jsx b/app/javascript/chat/__tests__/inviteForm.test.jsx
new file mode 100644
index 000000000..08a446849
--- /dev/null
+++ b/app/javascript/chat/__tests__/inviteForm.test.jsx
@@ -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) => (
+
+)
+
+describe('', () => {
+ 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)
+ })
+})
\ No newline at end of file
diff --git a/app/javascript/chat/__tests__/leaveMembershipSection.test.jsx b/app/javascript/chat/__tests__/leaveMembershipSection.test.jsx
new file mode 100644
index 000000000..7a358d932
--- /dev/null
+++ b/app/javascript/chat/__tests__/leaveMembershipSection.test.jsx
@@ -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) => (
+
+);
+
+describe('', () => {
+ 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);
+ });
+});
diff --git a/app/javascript/chat/__tests__/membership.test.jsx b/app/javascript/chat/__tests__/membership.test.jsx
new file mode 100644
index 000000000..34a6af248
--- /dev/null
+++ b/app/javascript/chat/__tests__/membership.test.jsx
@@ -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) => (
+
+);
+
+describe('', () => {
+ 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);
+ });
+});
diff --git a/app/javascript/chat/__tests__/modFaqSection.test.jsx b/app/javascript/chat/__tests__/modFaqSection.test.jsx
new file mode 100644
index 000000000..ab9478c20
--- /dev/null
+++ b/app/javascript/chat/__tests__/modFaqSection.test.jsx
@@ -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 (
+
+ );
+};
+
+describe('', () => {
+ 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);
+ });
+});
diff --git a/app/javascript/chat/__tests__/modSection.test.jsx b/app/javascript/chat/__tests__/modSection.test.jsx
new file mode 100644
index 000000000..9d1056664
--- /dev/null
+++ b/app/javascript/chat/__tests__/modSection.test.jsx
@@ -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 ;
+};
+
+describe('', () => {
+ 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);
+ });
+});
diff --git a/app/javascript/chat/__tests__/pendingMembershipsSection.test.jsx b/app/javascript/chat/__tests__/pendingMembershipsSection.test.jsx
new file mode 100644
index 000000000..5c3e22dc3
--- /dev/null
+++ b/app/javascript/chat/__tests__/pendingMembershipsSection.test.jsx
@@ -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) => (
+
+);
+
+describe('', () => {
+ 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);
+ });
+});
diff --git a/app/javascript/chat/__tests__/personalSetting.test.jsx b/app/javascript/chat/__tests__/personalSetting.test.jsx
new file mode 100644
index 000000000..dcd7cb998
--- /dev/null
+++ b/app/javascript/chat/__tests__/personalSetting.test.jsx
@@ -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 (
+
+ );
+};
+
+describe('', () => {
+ 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);
+ });
+});
diff --git a/app/javascript/chat/__tests__/requestedMembershipSection.test.jsx b/app/javascript/chat/__tests__/requestedMembershipSection.test.jsx
new file mode 100644
index 000000000..912b36939
--- /dev/null
+++ b/app/javascript/chat/__tests__/requestedMembershipSection.test.jsx
@@ -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) => (
+
+);
+
+describe('', () => {
+ 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);
+ });
+});
diff --git a/app/javascript/chat/__tests__/settingsForm.test.jsx b/app/javascript/chat/__tests__/settingsForm.test.jsx
new file mode 100644
index 000000000..ae6903430
--- /dev/null
+++ b/app/javascript/chat/__tests__/settingsForm.test.jsx
@@ -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 (
+
+ );
+};
+
+describe('', () => {
+ 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);
+ });
+});
diff --git a/app/javascript/chat/actions/chat_channel_setting_actions.js b/app/javascript/chat/actions/chat_channel_setting_actions.js
new file mode 100644
index 000000000..033e56a39
--- /dev/null
+++ b/app/javascript/chat/actions/chat_channel_setting_actions.js
@@ -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();
+}
diff --git a/app/javascript/chat/actions/requestActions.js b/app/javascript/chat/actions/requestActions.js
index eb0a0dc5d..dddef6aaf 100644
--- a/app/javascript/chat/actions/requestActions.js
+++ b/app/javascript/chat/actions/requestActions.js
@@ -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())
diff --git a/app/javascript/chat/chat.jsx b/app/javascript/chat/chat.jsx
index 46c82de5a..bb5c974b3 100644
--- a/app/javascript/chat/chat.jsx
+++ b/app/javascript/chat/chat.jsx
@@ -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 {
must
{' '}
- abide by the
+ abide by the
{' '}
code of conduct
.
@@ -1062,7 +1068,7 @@ export default class Chat extends Component {
return (
- You have joined
+ You have joined
{' '}
{activeChannel.channel_name}
! All interactions
@@ -1071,7 +1077,7 @@ export default class Chat extends Component {
must
{' '}
- abide by the
+ abide by the
{' '}
code of conduct
.
@@ -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 (
{activeChannel.channel_name}
@@ -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 (
(
-
@@ -69,27 +67,27 @@ export default class Content extends Component {
function display(props) {
const { resource } = props;
- if (resource.type_of === 'loading-user') {
- return
;
- }
- if (resource.type_of === 'article') {
- return
;
- }
- if (resource.type_of === 'channel-request') {
- return (
-
- );
- }
- if (resource.type_of === 'channel-request-manager') {
- return (
-
- );
+
+ switch(resource.type_of) {
+ case 'loading-user':
+ return
+ case 'article':
+ return
;
+ case 'channel-request':
+ return
;
+ case 'channel-request-manager':
+ return (
+
+ )
+ case 'chat-channel-setting':
+ return (
+
+ );
+ default:
+ return null;
}
}
diff --git a/app/policies/chat_channel_membership_policy.rb b/app/policies/chat_channel_membership_policy.rb
index a9fbd4c1a..3e4de83d2 100644
--- a/app/policies/chat_channel_membership_policy.rb
+++ b/app/policies/chat_channel_membership_policy.rb
@@ -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
diff --git a/app/policies/chat_channel_policy.rb b/app/policies/chat_channel_policy.rb
index 0cf701cb3..5f51a0d7f 100644
--- a/app/policies/chat_channel_policy.rb
+++ b/app/policies/chat_channel_policy.rb
@@ -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
diff --git a/app/presenters/chat_channel_detail_presenter.rb b/app/presenters/chat_channel_detail_presenter.rb
new file mode 100644
index 000000000..9e4f9b7e1
--- /dev/null
+++ b/app/presenters/chat_channel_detail_presenter.rb
@@ -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
diff --git a/app/presenters/membership_user_presenter.rb b/app/presenters/membership_user_presenter.rb
new file mode 100644
index 000000000..329056311
--- /dev/null
+++ b/app/presenters/membership_user_presenter.rb
@@ -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
diff --git a/app/services/application_service.rb b/app/services/application_service.rb
new file mode 100644
index 000000000..35a2c827a
--- /dev/null
+++ b/app/services/application_service.rb
@@ -0,0 +1,5 @@
+class ApplicationService
+ def self.perform(*args, &block)
+ new(*args, &block).perform
+ end
+end
diff --git a/app/services/chat_channel_update_service.rb b/app/services/chat_channel_update_service.rb
index 03a6d80f6..f2d4e3f52 100644
--- a/app/services/chat_channel_update_service.rb
+++ b/app/services/chat_channel_update_service.rb
@@ -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
diff --git a/config/application.rb b/config/application.rb
index 1ff42c4bc..f77fb0731 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -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"]
diff --git a/config/routes.rb b/config/routes.rb
index 9a378ad4b..cf0e455fa 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -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
diff --git a/spec/requests/chat_channel_memberships_spec.rb b/spec/requests/chat_channel_memberships_spec.rb
index f245e8a89..b5fb74e8b 100644
--- a/spec/requests/chat_channel_memberships_spec.rb
+++ b/spec/requests/chat_channel_memberships_spec.rb
@@ -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
diff --git a/spec/requests/chat_channels_spec.rb b/spec/requests/chat_channels_spec.rb
index a0554f705..af57dee35 100644
--- a/spec/requests/chat_channels_spec.rb
+++ b/spec/requests/chat_channels_spec.rb
@@ -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