✂✂✂ Remove Connect (#14734)
* Remove Connect
* Remove more Connect specs
* Remove a lot more Connect code
* 🚮
* It all has to go
* Explicitly add httpclient
* Update application layout
* Remove messages association from User
* Start fixing specs
* reintroduce util function and refactor references
* Remove Connect Cypress test
* Fix more specs
* Remove Connect from listings
* Ignore contact_via_connect column on listings
* Remove contact_via_connect usages
* Ignore mod_chat_channel_id on tags
* Drop Connect tables
* Remove email_connect_messages from user notification settings
* Re-add httpclient 2.8.3
This was mistakenly removed as a merge conflict
* Don't need to exclude removed chat channel file
* Remove unneeded style for chat channels
* Remove unneeded channel list prop type
* Remove chat channels index/connect-link from getPageEntries
* Re-add comment from httpclient in Gemfile
* Remove connect references from mailers
Tag Moderators no longer have a chat channel
No longer will users be notified about new messages (there won't be
any)
No longer will users be notified about channel invites (you can't
invite anyone anymore)
* Don't configure Pusher and remove PUSHER_* from .env_sample
since it's removed from gemfile, the Pusher constant will not resolve, if this is
configured in the environment variables we'll fail to boot.
Co-authored-by: Suzanne Aitchison <suzanne@forem.com>
Co-authored-by: Dan Uber <dan@forem.com>
This commit is contained in:
parent
83b4a6db81
commit
09828853f6
250 changed files with 176 additions and 15427 deletions
|
|
@ -123,13 +123,6 @@ GA_TRACKING_ID="Optional"
|
||||||
# (https://mailchimp.com/developer/)
|
# (https://mailchimp.com/developer/)
|
||||||
MAILCHIMP_API_KEY="Optional-valid"
|
MAILCHIMP_API_KEY="Optional-valid"
|
||||||
|
|
||||||
# Pusher for DEV Connect/notfications
|
|
||||||
# (https://pusher.com/docs)
|
|
||||||
PUSHER_APP_ID=
|
|
||||||
PUSHER_CLUSTER=
|
|
||||||
PUSHER_KEY=
|
|
||||||
PUSHER_SECRET=
|
|
||||||
|
|
||||||
# Google recaptcha (Optional)
|
# Google recaptcha (Optional)
|
||||||
# (https://developers.google.com/recaptcha/intro)
|
# (https://developers.google.com/recaptcha/intro)
|
||||||
RECAPTCHA_SECRET=
|
RECAPTCHA_SECRET=
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,5 @@ require:
|
||||||
Rails/HasManyOrHasOneDependent:
|
Rails/HasManyOrHasOneDependent:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'app/models/article.rb'
|
- 'app/models/article.rb'
|
||||||
- 'app/models/chat_channel.rb'
|
|
||||||
- 'app/models/organization.rb'
|
- 'app/models/organization.rb'
|
||||||
- 'app/models/user.rb'
|
- 'app/models/user.rb'
|
||||||
|
|
|
||||||
2
Gemfile
2
Gemfile
|
|
@ -48,6 +48,7 @@ gem "honeycomb-beeline", "~> 2.7.1" # Monitoring and Observability gem
|
||||||
gem "html_truncator", "~> 0.4" # Truncate an HTML string properly
|
gem "html_truncator", "~> 0.4" # Truncate an HTML string properly
|
||||||
gem "htmlentities", "~> 4.3", ">= 4.3.4" # A module for encoding and decoding (X)HTML entities
|
gem "htmlentities", "~> 4.3", ">= 4.3.4" # A module for encoding and decoding (X)HTML entities
|
||||||
gem "httparty", "~> 0.20" # Makes http fun! Also, makes consuming restful web services dead easy
|
gem "httparty", "~> 0.20" # Makes http fun! Also, makes consuming restful web services dead easy
|
||||||
|
gem "httpclient", "~> 2.8.3" # Gives something like the functionality of libwww-perl (LWP) in Ruby
|
||||||
gem "i18n-js", "~> 3.9.0" # Helps with internationalization in Rails.
|
gem "i18n-js", "~> 3.9.0" # Helps with internationalization in Rails.
|
||||||
gem "imgproxy", "~> 2.0" # A gem that easily generates imgproxy URLs for your images
|
gem "imgproxy", "~> 2.0" # A gem that easily generates imgproxy URLs for your images
|
||||||
gem "inline_svg", "~> 1.7" # Embed SVG documents in your Rails views and style them with CSS
|
gem "inline_svg", "~> 1.7" # Embed SVG documents in your Rails views and style them with CSS
|
||||||
|
|
@ -74,7 +75,6 @@ gem "pg_search", "~> 2.3.5" # PgSearch builds Active Record named scopes that ta
|
||||||
gem "pghero", "~> 2.8" # Dashboard for Postgres
|
gem "pghero", "~> 2.8" # Dashboard for Postgres
|
||||||
gem "puma", "~> 5.5.2" # Puma is a simple, fast, threaded, and highly concurrent HTTP 1.1 server
|
gem "puma", "~> 5.5.2" # Puma is a simple, fast, threaded, and highly concurrent HTTP 1.1 server
|
||||||
gem "pundit", "~> 2.1" # Object oriented authorization for Rails applications
|
gem "pundit", "~> 2.1" # Object oriented authorization for Rails applications
|
||||||
gem "pusher", "~> 2.0" # Ruby library for Pusher Channels HTTP API
|
|
||||||
gem "rack-attack", "~> 6.5.0" # Used to throttle requests to prevent brute force attacks
|
gem "rack-attack", "~> 6.5.0" # Used to throttle requests to prevent brute force attacks
|
||||||
gem "rack-cors", "~> 1.1" # Middleware that will make Rack-based apps CORS compatible
|
gem "rack-cors", "~> 1.1" # Middleware that will make Rack-based apps CORS compatible
|
||||||
gem "rack-timeout", "~> 0.6" # Rack middleware which aborts requests that have been running for longer than a specified timeout
|
gem "rack-timeout", "~> 0.6" # Rack middleware which aborts requests that have been running for longer than a specified timeout
|
||||||
|
|
|
||||||
|
|
@ -561,11 +561,6 @@ GEM
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
pundit-matchers (1.7.0)
|
pundit-matchers (1.7.0)
|
||||||
rspec-rails (>= 3.0.0)
|
rspec-rails (>= 3.0.0)
|
||||||
pusher (2.0.2)
|
|
||||||
httpclient (~> 2.8)
|
|
||||||
multi_json (~> 1.15)
|
|
||||||
pusher-signature (~> 0.1.8)
|
|
||||||
pusher-signature (0.1.8)
|
|
||||||
raabro (1.4.0)
|
raabro (1.4.0)
|
||||||
racc (1.6.0)
|
racc (1.6.0)
|
||||||
rack (2.2.3)
|
rack (2.2.3)
|
||||||
|
|
@ -946,6 +941,7 @@ DEPENDENCIES
|
||||||
html_truncator (~> 0.4)
|
html_truncator (~> 0.4)
|
||||||
htmlentities (~> 4.3, >= 4.3.4)
|
htmlentities (~> 4.3, >= 4.3.4)
|
||||||
httparty (~> 0.20)
|
httparty (~> 0.20)
|
||||||
|
httpclient (~> 2.8.3)
|
||||||
hypershield (~> 0.2.2)
|
hypershield (~> 0.2.2)
|
||||||
i18n-js (~> 3.9.0)
|
i18n-js (~> 3.9.0)
|
||||||
i18n-tasks (~> 0.9.35)
|
i18n-tasks (~> 0.9.35)
|
||||||
|
|
@ -982,7 +978,6 @@ DEPENDENCIES
|
||||||
puma (~> 5.5.2)
|
puma (~> 5.5.2)
|
||||||
pundit (~> 2.1)
|
pundit (~> 2.1)
|
||||||
pundit-matchers (~> 1.7)
|
pundit-matchers (~> 1.7)
|
||||||
pusher (~> 2.0)
|
|
||||||
rack-attack (~> 6.5.0)
|
rack-attack (~> 6.5.0)
|
||||||
rack-cors (~> 1.1)
|
rack-cors (~> 1.1)
|
||||||
rack-host-redirect (~> 1.3)
|
rack-host-redirect (~> 1.3)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
global initializeLocalStorageRender, initializeBodyData,
|
global initializeLocalStorageRender, initializeBodyData,
|
||||||
initializeAllChatButtons, initializeAllTagEditButtons, initializeUserFollowButts,
|
initializeAllTagEditButtons, initializeUserFollowButts,
|
||||||
initializeBaseTracking, initializeCommentsPage,
|
initializeBaseTracking, initializeCommentsPage,
|
||||||
initializeArticleDate, initializeArticleReactions, initNotifications,
|
initializeArticleDate, initializeArticleReactions, initNotifications,
|
||||||
initializeCommentDate, initializeSettings,
|
initializeCommentDate, initializeSettings,
|
||||||
|
|
@ -50,7 +50,6 @@ function initializePage() {
|
||||||
clearInterval(waitingForDataLoad);
|
clearInterval(waitingForDataLoad);
|
||||||
if (document.body.getAttribute('data-user-status') === 'logged-in') {
|
if (document.body.getAttribute('data-user-status') === 'logged-in') {
|
||||||
initializeBaseUserData();
|
initializeBaseUserData();
|
||||||
initializeAllChatButtons();
|
|
||||||
initializeAllTagEditButtons();
|
initializeAllTagEditButtons();
|
||||||
}
|
}
|
||||||
initializeBroadcast();
|
initializeBroadcast();
|
||||||
|
|
|
||||||
|
|
@ -1,119 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
function showChatModal(modal) {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
modal.style.display = 'block';
|
|
||||||
document.getElementById('new-message').focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideChatModal(modal) {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
modal.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleModal() {
|
|
||||||
var modal = document.getElementsByClassName('crayons-modal')[0];
|
|
||||||
modal.classList.toggle('hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
function initModal() {
|
|
||||||
var modal = document.getElementsByClassName('crayons-modal')[0];
|
|
||||||
modal
|
|
||||||
.getElementsByClassName('close-modal')[0]
|
|
||||||
.addEventListener('click', toggleModal);
|
|
||||||
modal
|
|
||||||
.getElementsByClassName('crayons-modal__overlay')[0]
|
|
||||||
.addEventListener('click', toggleModal);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleChatButtonPress(form) {
|
|
||||||
var message = document.getElementById('new-message').value;
|
|
||||||
var formDataInfo = JSON.parse(form.dataset.info);
|
|
||||||
var formData = new FormData();
|
|
||||||
|
|
||||||
if (message.replace(/\s/g, '').length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
formData.append('user_id', formDataInfo.id);
|
|
||||||
formData.append('message', message);
|
|
||||||
formData.append('controller', 'chat_channels');
|
|
||||||
|
|
||||||
getCsrfToken()
|
|
||||||
.then(sendFetch('chat-creation', formData))
|
|
||||||
.then(() => {
|
|
||||||
window.location.href = `/connect/@${formDataInfo.username}`;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function addButtonClickHandle(response, button, modalInfo) {
|
|
||||||
var linkWrap = document.getElementById('user-connect-redirect');
|
|
||||||
var form = document.getElementById('new-message-form');
|
|
||||||
button.classList.add('showing');
|
|
||||||
if (modalInfo.showChat === 'open' && response !== 'mutual') {
|
|
||||||
linkWrap.removeAttribute('href'); // remove link
|
|
||||||
button.addEventListener('click', toggleModal);
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
button.classList.remove('hidden'); // show button
|
|
||||||
linkWrap.classList.remove('hidden'); // show button
|
|
||||||
form.onsubmit = () => {
|
|
||||||
handleChatButtonPress(form);
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
} else if (response === 'mutual') {
|
|
||||||
button.removeEventListener('click', toggleModal);
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
button.classList.remove('hidden'); // show button
|
|
||||||
linkWrap.classList.remove('hidden'); // show button
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function fetchButton(button, modalInfo) {
|
|
||||||
var dataRequester;
|
|
||||||
// button.dataset.fetched = 'fetched'; // telling initialize that this button has been fetched
|
|
||||||
if (window.XMLHttpRequest) {
|
|
||||||
dataRequester = new XMLHttpRequest();
|
|
||||||
} else {
|
|
||||||
dataRequester = new ActiveXObject('Microsoft.XMLHTTP');
|
|
||||||
}
|
|
||||||
dataRequester.onreadystatechange = () => {
|
|
||||||
if (
|
|
||||||
dataRequester.readyState === XMLHttpRequest.DONE &&
|
|
||||||
dataRequester.status === 200
|
|
||||||
) {
|
|
||||||
addButtonClickHandle(dataRequester.response, button, modalInfo);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
dataRequester.open(
|
|
||||||
'GET',
|
|
||||||
'/follows/' + modalInfo.id + '?followable_type=' + modalInfo.className,
|
|
||||||
);
|
|
||||||
dataRequester.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
function initializeChatButton(button, modalInfo) {
|
|
||||||
// if user logged out, do nothing
|
|
||||||
var user = userData();
|
|
||||||
if (user === null || user.id === modalInfo.id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fetchButton(button, modalInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
// finds all elements with chat action button class
|
|
||||||
function initializeAllChatButtons() {
|
|
||||||
var buttons = document.getElementsByClassName('chat-action-button');
|
|
||||||
var modal = document.getElementById('new-message-form');
|
|
||||||
var i;
|
|
||||||
|
|
||||||
if (!modal) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var modalInfo = JSON.parse(modal.dataset.info);
|
|
||||||
initModal();
|
|
||||||
|
|
||||||
for (i = 0; i < buttons.length; i += 1) {
|
|
||||||
initializeChatButton(buttons[i], modalInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -46,12 +46,6 @@ function sendFetch(switchStatement, body) {
|
||||||
addTokenToBody: true,
|
addTokenToBody: true,
|
||||||
body,
|
body,
|
||||||
});
|
});
|
||||||
case 'chat-creation':
|
|
||||||
return fetchCallback({
|
|
||||||
url: '/chat_channels/create_chat',
|
|
||||||
addTokenToBody: true,
|
|
||||||
body,
|
|
||||||
});
|
|
||||||
case 'block-user':
|
case 'block-user':
|
||||||
return fetchCallback({
|
return fetchCallback({
|
||||||
url: '/user_blocks',
|
url: '/user_blocks',
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,134 +0,0 @@
|
||||||
@import '../variables';
|
|
||||||
|
|
||||||
.channeldetails {
|
|
||||||
.channeldetails__name {
|
|
||||||
font-family: var(--ff-sans-serif);
|
|
||||||
font-size: 48px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.channeldetails__description {
|
|
||||||
font-family: var(--ff-sans-serif);
|
|
||||||
color: $dark-gray;
|
|
||||||
}
|
|
||||||
|
|
||||||
// channel users
|
|
||||||
.channeldetails__user {
|
|
||||||
a {
|
|
||||||
padding: 0 4px !important;
|
|
||||||
border-radius: 3px;
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
padding: 2px 0;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
.channeldetails__userprofileimage {
|
|
||||||
height: 25px;
|
|
||||||
width: 25px;
|
|
||||||
margin-right: 6px;
|
|
||||||
border-radius: 20px;
|
|
||||||
vertical-align: -6px;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
font-family: var(--ff-sans-serif);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.channeldetails__inviteusers {
|
|
||||||
h2 {
|
|
||||||
font-family: var(--ff-sans-serif);
|
|
||||||
}
|
|
||||||
|
|
||||||
// find users
|
|
||||||
input {
|
|
||||||
border-radius: 3px;
|
|
||||||
border: 1px solid #dbdbdb;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 8px;
|
|
||||||
margin-left: 2px;
|
|
||||||
font-size: 18px;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.channeldetails__searchresults {
|
|
||||||
padding: 5px 0;
|
|
||||||
|
|
||||||
.channeldetails__searchedusers {
|
|
||||||
padding: 1px 0;
|
|
||||||
|
|
||||||
// profile pic
|
|
||||||
img {
|
|
||||||
height: 22px;
|
|
||||||
width: 22px;
|
|
||||||
margin-right: 6px;
|
|
||||||
border-radius: 20px;
|
|
||||||
vertical-align: -6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
font-size: 14px;
|
|
||||||
font-family: var(--ff-sans-serif);
|
|
||||||
}
|
|
||||||
|
|
||||||
// invite button
|
|
||||||
button {
|
|
||||||
width: 45px;
|
|
||||||
height: 24px;
|
|
||||||
background-color: $purple;
|
|
||||||
border-radius: 3px;
|
|
||||||
color: $white;
|
|
||||||
}
|
|
||||||
|
|
||||||
// in channel message
|
|
||||||
span {
|
|
||||||
color: $dark-gray;
|
|
||||||
font-size: 14px;
|
|
||||||
font-family: var(--ff-sans-serif);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.channeldetails__pendingusers {
|
|
||||||
a {
|
|
||||||
font-size: 18px;
|
|
||||||
font-family: var(--ff-sans-serif);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.channeldetails__leavechannel {
|
|
||||||
// Danger Zone
|
|
||||||
h2 {
|
|
||||||
color: $red;
|
|
||||||
font-family: $helvetica-condensed;
|
|
||||||
font-size: 2.5em;
|
|
||||||
}
|
|
||||||
// leave channel
|
|
||||||
button {
|
|
||||||
width: 180px;
|
|
||||||
padding: 3px 0px;
|
|
||||||
border-radius: 3px;
|
|
||||||
background-color: $red;
|
|
||||||
color: $white;
|
|
||||||
font-size: 1.2em;
|
|
||||||
font-weight: bold;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.channeldetails__leftchannel {
|
|
||||||
h2 {
|
|
||||||
font-family: var(--ff-sans-serif);
|
|
||||||
font-size: 32px;
|
|
||||||
}
|
|
||||||
h3 {
|
|
||||||
font-family: var(--ff-sans-serif);
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
h4 {
|
|
||||||
font-family: var(--ff-sans-serif);
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
font-family: var(--ff-sans-serif);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -19,8 +19,6 @@
|
||||||
|
|
||||||
@import 'ltags/LiquidTags';
|
@import 'ltags/LiquidTags';
|
||||||
|
|
||||||
@import 'chat';
|
|
||||||
|
|
||||||
@import 'email_subscriptions';
|
@import 'email_subscriptions';
|
||||||
|
|
||||||
@import 'runtime';
|
@import 'runtime';
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,6 @@ body.dark-theme {
|
||||||
.image-upload-button button,
|
.image-upload-button button,
|
||||||
.icon-image,
|
.icon-image,
|
||||||
.dev-badge,
|
.dev-badge,
|
||||||
.chatchannels__config img,
|
|
||||||
.message__actions img {
|
.message__actions img {
|
||||||
-webkit-filter: invert(95%);
|
-webkit-filter: invert(95%);
|
||||||
filter: invert(95%);
|
filter: invert(95%);
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ module Admin
|
||||||
articles: "https://admin.forem.com/docs/forem-basics/posts",
|
articles: "https://admin.forem.com/docs/forem-basics/posts",
|
||||||
badges: "https://admin.forem.com/docs/forem-basics/badges",
|
badges: "https://admin.forem.com/docs/forem-basics/badges",
|
||||||
badge_achievements: "https://admin.forem.com/docs/forem-basics/badges",
|
badge_achievements: "https://admin.forem.com/docs/forem-basics/badges",
|
||||||
chat_channels: "https://admin.forem.com/docs/advanced-customization/chat-channels",
|
|
||||||
display_ads: "https://admin.forem.com/docs/advanced-customization/display-ads",
|
display_ads: "https://admin.forem.com/docs/advanced-customization/display-ads",
|
||||||
feedback_messages: "https://admin.forem.com/docs/advanced-customization/reports",
|
feedback_messages: "https://admin.forem.com/docs/advanced-customization/reports",
|
||||||
html_variants: "https://admin.forem.com/docs/advanced-customization/html-variants",
|
html_variants: "https://admin.forem.com/docs/advanced-customization/html-variants",
|
||||||
|
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
module Admin
|
|
||||||
class ChatChannelsController < Admin::ApplicationController
|
|
||||||
layout "admin"
|
|
||||||
|
|
||||||
CHAT_ALLOWED_PARAMS = %i[usernames_string channel_name username_string].freeze
|
|
||||||
|
|
||||||
def index
|
|
||||||
@q = ChatChannel.where(channel_type: "invite_only").includes(:users).ransack(params[:q])
|
|
||||||
@group_chat_channels = @q.result.page(params[:page]).per(50)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create
|
|
||||||
ChatChannels::CreateWithUsers.call(
|
|
||||||
users: users_by_param,
|
|
||||||
channel_type: "invite_only",
|
|
||||||
contrived_name: chat_channel_params[:channel_name],
|
|
||||||
membership_role: "mod",
|
|
||||||
)
|
|
||||||
redirect_back(fallback_location: admin_chat_channels_path)
|
|
||||||
end
|
|
||||||
|
|
||||||
def update
|
|
||||||
@chat_channel = ChatChannel.find(params[:id])
|
|
||||||
@chat_channel.invite_users(users: users_by_param)
|
|
||||||
redirect_back(fallback_location: admin_chat_channels_path)
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove_user
|
|
||||||
@chat_channel = ChatChannel.find(params[:id])
|
|
||||||
@chat_channel.remove_user(user_by_param)
|
|
||||||
redirect_back(fallback_location: admin_chat_channels_path)
|
|
||||||
end
|
|
||||||
|
|
||||||
def destroy
|
|
||||||
@chat_channel = ChatChannel.find(params[:id])
|
|
||||||
if @chat_channel.users.count.zero?
|
|
||||||
@chat_channel.destroy
|
|
||||||
flash[:success] = "Channel was successfully deleted."
|
|
||||||
else
|
|
||||||
flash[:alert] = "Channel NOT deleted, because it still has users."
|
|
||||||
end
|
|
||||||
redirect_back(fallback_location: admin_chat_channels_path)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def users_by_param
|
|
||||||
User.where(username: chat_channel_params[:usernames_string].downcase.delete(" ").split(","))
|
|
||||||
end
|
|
||||||
|
|
||||||
def user_by_param
|
|
||||||
User.find_by(username: chat_channel_params[:username_string])
|
|
||||||
end
|
|
||||||
|
|
||||||
def chat_channel_params
|
|
||||||
params.require(:chat_channel).permit(CHAT_ALLOWED_PARAMS)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,285 +0,0 @@
|
||||||
class ChatChannelMembershipsController < ApplicationController
|
|
||||||
before_action :authenticate_user!
|
|
||||||
after_action :verify_authorized, except: %w[join_channel request_details]
|
|
||||||
|
|
||||||
include MessagesHelper
|
|
||||||
include ChatChannelMembershipsHelper
|
|
||||||
|
|
||||||
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
|
|
||||||
rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
|
|
||||||
|
|
||||||
def index
|
|
||||||
skip_authorization
|
|
||||||
memberships = current_user.chat_channel_memberships.includes(:chat_channel)
|
|
||||||
@pending_invites = memberships.filter_by_status("pending")
|
|
||||||
end
|
|
||||||
|
|
||||||
def find_by_chat_channel_id
|
|
||||||
@membership = ChatChannelMembership.where(chat_channel_id: params[:chat_channel_id],
|
|
||||||
user_id: current_user.id).first!
|
|
||||||
authorize @membership
|
|
||||||
render json: @membership.to_json(
|
|
||||||
only: %i[id status viewable_by chat_channel_id last_opened_at],
|
|
||||||
methods: %i[channel_text channel_last_message_at channel_status channel_username
|
|
||||||
channel_type channel_text channel_name channel_image channel_modified_slug channel_messages_count],
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def chat_channel_info
|
|
||||||
@membership = ChatChannelMembership.find(params[:id])
|
|
||||||
authorize @membership
|
|
||||||
@channel = @membership.chat_channel
|
|
||||||
invite_cache_key = "chat-channel-invite-#{@channel.id}"
|
|
||||||
invitation_slug = Rails.cache.fetch(invite_cache_key, expires_in: 80.hours) do
|
|
||||||
"invitation-link-#{SecureRandom.hex(3)}"
|
|
||||||
end
|
|
||||||
@invitation_link = "/join_channel_invitation/#{@channel.slug}?invitation_slug=#{invitation_slug}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_membership_request
|
|
||||||
chat_channel = ChatChannel.find_by(id: channel_membership_params[:chat_channel_id])
|
|
||||||
authorize chat_channel, :update?
|
|
||||||
message = ChatChannels::SendInvitation.call(
|
|
||||||
channel_membership_params[:invitation_usernames],
|
|
||||||
current_user,
|
|
||||||
chat_channel,
|
|
||||||
)
|
|
||||||
|
|
||||||
render json: { success: true, message: message, data: {} }, status: :ok
|
|
||||||
end
|
|
||||||
|
|
||||||
def join_channel
|
|
||||||
membership_params = params[:chat_channel_membership]
|
|
||||||
chat_channel = ChatChannel.find(membership_params[:chat_channel_id])
|
|
||||||
existing_membership = ChatChannelMembership.find_by(user_id: current_user.id, chat_channel_id: chat_channel.id)
|
|
||||||
if existing_membership.present? && %w[active joining_request].exclude?(existing_membership.status)
|
|
||||||
status = existing_membership.update(status: "joining_request", role: "member")
|
|
||||||
else
|
|
||||||
membership = ChatChannelMembership.new(user_id: current_user.id, chat_channel_id: chat_channel.id,
|
|
||||||
role: "member", status: "joining_request")
|
|
||||||
status = membership.save
|
|
||||||
end
|
|
||||||
if status
|
|
||||||
render json: { status: "success", message: "Request Sent" }, status: :ok
|
|
||||||
else
|
|
||||||
render json: { status: 400, message: "Unable to join channel" }, status: :bad_request
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove_membership
|
|
||||||
@chat_channel = ChatChannel.find(params[:chat_channel_id])
|
|
||||||
authorize @chat_channel, :update?
|
|
||||||
@chat_channel_membership = @chat_channel.chat_channel_memberships.find(params[:membership_id])
|
|
||||||
if params[:status] == "pending"
|
|
||||||
@chat_channel_membership.destroy
|
|
||||||
message = "Invitation removed."
|
|
||||||
else
|
|
||||||
membership = @chat_channel_membership
|
|
||||||
message = "@#{current_user.username} removed @#{membership.user.username} from #{membership.channel_name}"
|
|
||||||
send_chat_action_message(
|
|
||||||
message, current_user, @chat_channel_membership.chat_channel_id, "removed_from_channel"
|
|
||||||
)
|
|
||||||
@chat_channel_membership.update(status: "removed_from_channel")
|
|
||||||
message = "Removed #{@chat_channel_membership.user.name}"
|
|
||||||
end
|
|
||||||
|
|
||||||
render json: { status: "success", message: message, success: true }, status: :ok
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_membership
|
|
||||||
@chat_channel = ChatChannel.find(params[:chat_channel_id])
|
|
||||||
authorize @chat_channel, :update?
|
|
||||||
@chat_channel_membership = @chat_channel.chat_channel_memberships.find(params[:membership_id])
|
|
||||||
|
|
||||||
return unless permitted_params[:user_action].present? && @chat_channel_membership.status == "joining_request"
|
|
||||||
|
|
||||||
respond_to_invitation(@chat_channel_membership.status)
|
|
||||||
end
|
|
||||||
|
|
||||||
def update
|
|
||||||
@chat_channel_membership = ChatChannelMembership.find(params[:id])
|
|
||||||
authorize @chat_channel_membership
|
|
||||||
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
|
|
||||||
render json: { success: true, message: "Personal settings updated." }, status: :ok
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
def request_details
|
|
||||||
user_chat_channels = ChatChannel.includes(:chat_channel_memberships).where(
|
|
||||||
chat_channel_memberships: { user_id: current_user.id, role: "mod", status: "active" },
|
|
||||||
)
|
|
||||||
@memberships = user_chat_channels.flat_map(&:requested_memberships)
|
|
||||||
@user_invitations = ChatChannelMembership.where(
|
|
||||||
user_id: current_user.id,
|
|
||||||
status: %w[pending],
|
|
||||||
).order("created_at DESC")
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_membership_role
|
|
||||||
@chat_channel = ChatChannel.find_by(id: params[:id])
|
|
||||||
authorize @chat_channel, :update?
|
|
||||||
membership = ChatChannelMembership.find_by(
|
|
||||||
id: channel_membership_params[:membership_id],
|
|
||||||
chat_channel_id: @chat_channel.id,
|
|
||||||
)
|
|
||||||
|
|
||||||
membership.update(role: channel_membership_params[:role])
|
|
||||||
if membership.errors.any?
|
|
||||||
render json: {
|
|
||||||
success: false,
|
|
||||||
message: "Failed to update membership",
|
|
||||||
errors: chat_channel_membership.errors.full_messages
|
|
||||||
}, status: :bad_request
|
|
||||||
else
|
|
||||||
role = membership.reload.role
|
|
||||||
send_chat_action_message(
|
|
||||||
"@#{membership.user.username} role is updated as #{role}",
|
|
||||||
current_user, @chat_channel.id,
|
|
||||||
"updated"
|
|
||||||
)
|
|
||||||
|
|
||||||
render json: { success: true, message: "User Membership is updated" }, status: :ok
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def join_channel_invitation
|
|
||||||
@chat_channel = ChatChannel.find_by(slug: params[:channel_slug])
|
|
||||||
authorize @chat_channel
|
|
||||||
invite_cache_key = "chat-channel-invite-#{@chat_channel.id}"
|
|
||||||
invitation_slug = Rails.cache.read(invite_cache_key)
|
|
||||||
existing_membership = ChatChannelMembership.find_by(user_id: current_user.id, chat_channel_id: @chat_channel.id)
|
|
||||||
redirect_to URI.parse("/connect/#{@chat_channel.slug}").path if existing_membership&.status == "active"
|
|
||||||
@link_expired = true if invitation_slug != params[:invitation_slug]
|
|
||||||
end
|
|
||||||
|
|
||||||
def joining_invitation_response
|
|
||||||
chat_channel = ChatChannel.find_by(id: params[:chat_channel_id])
|
|
||||||
authorize chat_channel
|
|
||||||
if params[:user_action] == "accept"
|
|
||||||
membership = ChatChannelMembership.find_by(user_id: current_user.id, chat_channel_id: chat_channel.id)
|
|
||||||
if !membership
|
|
||||||
membership = ChatChannelMembership.create(user_id: current_user.id, chat_channel_id: chat_channel.id)
|
|
||||||
unless membership&.errors&.any?
|
|
||||||
send_chat_action_message("@#{membership.user.username} join the channel", current_user, chat_channel.id,
|
|
||||||
"joined")
|
|
||||||
end
|
|
||||||
elsif membership.status != "active"
|
|
||||||
# This check checks if the user already has the chatChannelMembership with the status pending, joining_request
|
|
||||||
# Then update it to as active.
|
|
||||||
membership.update(role: "member", status: "active")
|
|
||||||
send_chat_action_message("@#{membership.user.username} join the channel", current_user,
|
|
||||||
membership.chat_channel_id, "joined")
|
|
||||||
end
|
|
||||||
|
|
||||||
if membership&.errors&.any?
|
|
||||||
flash[:settings_notice] = membership.errors.full_messages
|
|
||||||
redirect_to root_path
|
|
||||||
end
|
|
||||||
|
|
||||||
redirect_to connect_path(chat_channel.slug)
|
|
||||||
else
|
|
||||||
redirect_to root_path
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def permitted_params
|
|
||||||
params.require(:chat_channel_membership).permit(:user_action, :show_global_badge_notification)
|
|
||||||
end
|
|
||||||
|
|
||||||
def channel_membership_params
|
|
||||||
params.require(:chat_channel_membership).permit(:chat_channel_id, :invitation_usernames, :membership_id, :role)
|
|
||||||
end
|
|
||||||
|
|
||||||
def respond_to_invitation(previous_status)
|
|
||||||
if permitted_params[:user_action] == "accept"
|
|
||||||
@chat_channel_membership.update(status: "active")
|
|
||||||
channel_name = @chat_channel_membership.chat_channel.channel_name
|
|
||||||
|
|
||||||
if previous_status == "pending"
|
|
||||||
send_chat_action_message(
|
|
||||||
"@#{current_user.username} joined #{@chat_channel_membership.channel_name}",
|
|
||||||
current_user,
|
|
||||||
@chat_channel_membership.chat_channel_id,
|
|
||||||
"joined",
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
notice = "Accepted request of #{@chat_channel_membership.user.username} to join #{channel_name}."
|
|
||||||
end
|
|
||||||
else
|
|
||||||
@chat_channel_membership.update(status: "rejected")
|
|
||||||
notice = "Invitation rejected."
|
|
||||||
end
|
|
||||||
|
|
||||||
membership_user = format_membership(@chat_channel_membership)
|
|
||||||
flash[:settings_notice] = notice
|
|
||||||
|
|
||||||
respond_to do |format|
|
|
||||||
format.html { redirect_to chat_channel_memberships_path }
|
|
||||||
format.json do
|
|
||||||
render json: {
|
|
||||||
status: "success",
|
|
||||||
message: flash[:settings_notice],
|
|
||||||
success: true,
|
|
||||||
membership: membership_user
|
|
||||||
}, status: :ok
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def send_chat_action_message(message, user, channel_id, action)
|
|
||||||
temp_message_id = SecureRandom.hex(20)
|
|
||||||
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) unless message.left_channel?
|
|
||||||
end
|
|
||||||
|
|
||||||
def user_not_authorized
|
|
||||||
render json: { success: false, message: "User not authorized" }, status: :unauthorized
|
|
||||||
end
|
|
||||||
|
|
||||||
def record_not_found
|
|
||||||
render json: { success: false, message: "not found" }, status: :not_found
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,268 +0,0 @@
|
||||||
class ChatChannelsController < ApplicationController
|
|
||||||
before_action :authenticate_user!, only: %i[moderate]
|
|
||||||
before_action :set_channel, only: %i[show update update_channel open moderate]
|
|
||||||
after_action :verify_authorized
|
|
||||||
|
|
||||||
include MessagesHelper
|
|
||||||
include ChatChannelsHelper
|
|
||||||
|
|
||||||
CHANNEL_ATTRIBUTES_FOR_SERIALIZATION = %i[id description channel_name].freeze
|
|
||||||
private_constant :CHANNEL_ATTRIBUTES_FOR_SERIALIZATION
|
|
||||||
|
|
||||||
def index
|
|
||||||
case params[:state]
|
|
||||||
when "unopened"
|
|
||||||
authorize ChatChannel
|
|
||||||
@chat_channels_memberships = unopened_json_response
|
|
||||||
render "index", formats: :json
|
|
||||||
when "unopened_ids"
|
|
||||||
authorize ChatChannel
|
|
||||||
@unopened_ids = unopened_ids_response
|
|
||||||
render json: { unopened_ids: @unopened_ids }
|
|
||||||
when "pending"
|
|
||||||
authorize ChatChannel
|
|
||||||
@chat_channels_memberships = pending_json_response
|
|
||||||
render "index", formats: :json
|
|
||||||
when "joining_request"
|
|
||||||
authorize ChatChannel
|
|
||||||
@chat_channels_memberships = joining_request_json_response
|
|
||||||
render "index", formats: :json
|
|
||||||
else
|
|
||||||
skip_authorization
|
|
||||||
render_channels_html
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def show
|
|
||||||
@chat_messages = @chat_channel.messages
|
|
||||||
.includes(:user)
|
|
||||||
.order(created_at: :desc)
|
|
||||||
.offset(params[:message_offset])
|
|
||||||
.limit(50)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create
|
|
||||||
authorize ChatChannel
|
|
||||||
@chat_channel = current_user.chat_channels.create(
|
|
||||||
channel_type: "invite_only",
|
|
||||||
channel_name: chat_channel_params[:channel_name],
|
|
||||||
slug: chat_channel_params[:slug],
|
|
||||||
)
|
|
||||||
render_chat_channel
|
|
||||||
end
|
|
||||||
|
|
||||||
def update
|
|
||||||
chat_channel = ChatChannels::UpdateChannel.call(@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: Settings::General.mascot_user_id, chat_channel_id: chat_channel.id,
|
|
||||||
role: "member", status: "active")
|
|
||||||
else
|
|
||||||
ChatChannelMembership.find_by(user_id: Settings::General.mascot_user_id)&.destroy
|
|
||||||
end
|
|
||||||
flash[:settings_notice] = "Channel settings updated."
|
|
||||||
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.update(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: Settings::General.mascot_user_id, chat_channel_id: @chat_channel.id,
|
|
||||||
role: "member", status: "active")
|
|
||||||
else
|
|
||||||
ChatChannelMembership.find_by(user_id: Settings::General.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)
|
|
||||||
send_open_notification
|
|
||||||
render json: { status: "success", channel: params[:id] }, status: :ok
|
|
||||||
end
|
|
||||||
|
|
||||||
def moderate
|
|
||||||
chat_channel = ChatChannel.find_by(id: params[:id])
|
|
||||||
authorize chat_channel
|
|
||||||
command, username = chat_channel_params[:command].split
|
|
||||||
case command
|
|
||||||
when "/ban"
|
|
||||||
user = User.find_by(username: username)
|
|
||||||
membership = user&.chat_channel_memberships&.find_by(chat_channel: chat_channel)
|
|
||||||
if user && membership
|
|
||||||
user.add_role(:suspended)
|
|
||||||
user.messages.where(chat_channel: chat_channel).delete_all
|
|
||||||
membership.update(status: "removed_from_channel")
|
|
||||||
Pusher.trigger(chat_channel.pusher_channels, "user-banned", { userId: user.id }.to_json)
|
|
||||||
render json: { status: "moderation-success", message: "#{username} was suspended.", userId: user.id,
|
|
||||||
chatChannelId: chat_channel.id }, status: :ok
|
|
||||||
else
|
|
||||||
render json: {
|
|
||||||
status: "error",
|
|
||||||
message: "Suspend failed. user with username '#{username}' not found in this channel."
|
|
||||||
}, status: :bad_request
|
|
||||||
end
|
|
||||||
when "/unban"
|
|
||||||
user = User.find_by(username: username)
|
|
||||||
if user
|
|
||||||
user.remove_role(:suspended)
|
|
||||||
render json: { status: "moderation-success", message: "#{username} was unsuspended." }, status: :ok
|
|
||||||
else
|
|
||||||
render json: {
|
|
||||||
status: "error",
|
|
||||||
message: "Unsuspend failed. User with username '#{username}' not found in this channel."
|
|
||||||
}, status: :bad_request
|
|
||||||
end
|
|
||||||
when "/clearchannel"
|
|
||||||
@chat_channel.clear_channel
|
|
||||||
render json: { status: "success", message: "cleared!" }, status: :ok
|
|
||||||
else
|
|
||||||
render json: { status: "error", message: "invalid command" }, status: :bad_request
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_chat
|
|
||||||
chat_recipient = User.find(params[:user_id])
|
|
||||||
valid_listing = Listing.where(user_id: params[:user_id], contact_via_connect: true).limit(1)
|
|
||||||
authorize ChatChannel
|
|
||||||
|
|
||||||
if chat_recipient.setting.inbox_type == "open" || valid_listing.length == 1
|
|
||||||
chat = ChatChannels::CreateWithUsers.call(users: [current_user, chat_recipient], channel_type: "direct")
|
|
||||||
message_markdown = params[:message]
|
|
||||||
message = Message.new(
|
|
||||||
chat_channel: chat,
|
|
||||||
message_markdown: message_markdown,
|
|
||||||
user: current_user,
|
|
||||||
)
|
|
||||||
chat.messages.append(message)
|
|
||||||
render json: { status: "success", message: "chat channel created!" }, status: :ok
|
|
||||||
else
|
|
||||||
render json: { status: "error", message: "not allowed!" }, status: :bad_request
|
|
||||||
end
|
|
||||||
rescue StandardError => e
|
|
||||||
render json: { status: "error", message: e.message }, status: :bad_request
|
|
||||||
end
|
|
||||||
|
|
||||||
def block_chat
|
|
||||||
chat_channel = ChatChannel.find(params[:chat_id])
|
|
||||||
authorize chat_channel
|
|
||||||
chat_channel.status = "blocked"
|
|
||||||
chat_channel.save
|
|
||||||
render json: { status: "success", message: "chat channel blocked" }, status: :ok
|
|
||||||
end
|
|
||||||
|
|
||||||
# NOTE: this is part of an effort of moving some things from the external to
|
|
||||||
# the internal API. No behavior was changes as part of this refactoring, so
|
|
||||||
# this action is a bit unusual.
|
|
||||||
def channel_info
|
|
||||||
skip_authorization
|
|
||||||
|
|
||||||
@chat_channel =
|
|
||||||
ChatChannel
|
|
||||||
.select(CHANNEL_ATTRIBUTES_FOR_SERIALIZATION)
|
|
||||||
.find_by(id: params[:id])
|
|
||||||
|
|
||||||
return if @chat_channel&.has_member?(current_user)
|
|
||||||
|
|
||||||
render json: { error: "not found", status: 404 }, status: :not_found
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_channel
|
|
||||||
chat_channel_params = params[:chat_channel]
|
|
||||||
chat_channel_name = chat_channel_params[:channel_name].split.join("-")
|
|
||||||
chat_channel = ChatChannel.new(
|
|
||||||
channel_type: "invite_only",
|
|
||||||
channel_name: chat_channel_params[:channel_name],
|
|
||||||
slug: "#{chat_channel_name}-#{SecureRandom.hex(5)}",
|
|
||||||
)
|
|
||||||
authorize chat_channel
|
|
||||||
chat_channel.save
|
|
||||||
membership = chat_channel.chat_channel_memberships.new(user_id: current_user.id, role: "mod")
|
|
||||||
if membership.save
|
|
||||||
message = ChatChannels::SendInvitation.call(
|
|
||||||
chat_channel_params[:invitation_usernames],
|
|
||||||
current_user,
|
|
||||||
chat_channel,
|
|
||||||
)
|
|
||||||
|
|
||||||
send_chat_action_message(
|
|
||||||
"channel is created by #{current_user.username}",
|
|
||||||
current_user, membership.chat_channel_id,
|
|
||||||
"chat channel is created"
|
|
||||||
)
|
|
||||||
render json: {
|
|
||||||
success: true,
|
|
||||||
message: "Channel is created #{message ? "& #{message}" : nil}"
|
|
||||||
}, status: :ok
|
|
||||||
else
|
|
||||||
render json: {
|
|
||||||
success: false,
|
|
||||||
message: membership.errors_as_sentence
|
|
||||||
}, status: 445
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def set_channel
|
|
||||||
@chat_channel = ChatChannel.find_by(id: params[:id]) || not_found
|
|
||||||
authorize @chat_channel
|
|
||||||
end
|
|
||||||
|
|
||||||
def chat_channel_params
|
|
||||||
params.require(:chat_channel).permit(policy(ChatChannel).permitted_attributes)
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_channels_html
|
|
||||||
return unless current_user && params[:slug]
|
|
||||||
|
|
||||||
slug = if params[:slug]&.start_with?("@")
|
|
||||||
[current_user.username, params[:slug].delete("@")].sort.join("/")
|
|
||||||
else
|
|
||||||
params[:slug]
|
|
||||||
end
|
|
||||||
chat_channel = ChatChannel.find_by(slug: slug)
|
|
||||||
return unless chat_channel
|
|
||||||
|
|
||||||
membership = chat_channel.chat_channel_memberships.find_by(user_id: current_user.id)
|
|
||||||
@active_channel = membership&.status == "active" ? chat_channel : nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_chat_channel
|
|
||||||
if @chat_channel.valid?
|
|
||||||
render json: { status: "success",
|
|
||||||
chat_channel: @chat_channel.to_json(only: %i[channel_name slug]) },
|
|
||||||
status: :ok
|
|
||||||
else
|
|
||||||
render json: { errors: @chat_channel.errors.full_messages }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def send_open_notification
|
|
||||||
adjusted_slug = if @chat_channel.group?
|
|
||||||
@chat_channel.adjusted_slug
|
|
||||||
else
|
|
||||||
@chat_channel.adjusted_slug(current_user)
|
|
||||||
end
|
|
||||||
payload = { channel_type: @chat_channel.channel_type, adjusted_slug: adjusted_slug }.to_json
|
|
||||||
Pusher.trigger(ChatChannel.pm_notifications_channel(session_current_user_id), "message-opened", payload)
|
|
||||||
end
|
|
||||||
|
|
||||||
def send_chat_action_message(message, user, channel_id, action)
|
|
||||||
temp_message_id = SecureRandom.hex(20)
|
|
||||||
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
|
|
||||||
end
|
|
||||||
|
|
@ -80,7 +80,7 @@ module ListingsToolkit
|
||||||
|
|
||||||
ALLOWED_PARAMS = %i[
|
ALLOWED_PARAMS = %i[
|
||||||
title body_markdown listing_category_id tag_list
|
title body_markdown listing_category_id tag_list
|
||||||
expires_at contact_via_connect location organization_id action
|
expires_at location organization_id action
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
# Filter for a set of known safe params
|
# Filter for a set of known safe params
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ class EmailSubscriptionsController < ApplicationController
|
||||||
email_comment_notifications: "comment notifications",
|
email_comment_notifications: "comment notifications",
|
||||||
email_follower_notifications: "follower notifications",
|
email_follower_notifications: "follower notifications",
|
||||||
email_mention_notifications: "mention notifications",
|
email_mention_notifications: "mention notifications",
|
||||||
email_connect_messages: "#{Settings::Community.community_name} connect messages",
|
|
||||||
email_unread_notifications: "unread notifications",
|
email_unread_notifications: "unread notifications",
|
||||||
email_badge_notifications: "badge notifications"
|
email_badge_notifications: "badge notifications"
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ class ListingsController < ApplicationController
|
||||||
|
|
||||||
INDEX_JSON_OPTIONS = {
|
INDEX_JSON_OPTIONS = {
|
||||||
only: %i[
|
only: %i[
|
||||||
title processed_html tag_list category id user_id slug contact_via_connect location bumped_at
|
title processed_html tag_list category id user_id slug location bumped_at
|
||||||
originally_published_at
|
originally_published_at
|
||||||
],
|
],
|
||||||
methods: %i[category],
|
methods: %i[category],
|
||||||
|
|
|
||||||
|
|
@ -1,117 +0,0 @@
|
||||||
class MessagesController < ApplicationController
|
|
||||||
before_action :set_message, only: %i[destroy update]
|
|
||||||
before_action :authenticate_user!, only: %i[create]
|
|
||||||
include MessagesHelper
|
|
||||||
|
|
||||||
def create
|
|
||||||
@message = Message.new(message_params)
|
|
||||||
@message.user_id = session_current_user_id
|
|
||||||
@temp_message_id = (0...20).map { ("a".."z").to_a[rand(8)] }.join
|
|
||||||
authorize @message
|
|
||||||
|
|
||||||
# sending temp message only to sender
|
|
||||||
pusher_message_created(true, @message, @temp_message_id)
|
|
||||||
if @message.save
|
|
||||||
pusher_message_created(false, @message, @temp_message_id)
|
|
||||||
notify_mentioned_users(@mentioned_users_id)
|
|
||||||
render json: { status: "success", message: { temp_id: @temp_message_id, id: @message.id } }, status: :created
|
|
||||||
else
|
|
||||||
render json: {
|
|
||||||
status: "error",
|
|
||||||
message: {
|
|
||||||
chat_channel_id: @message.chat_channel_id,
|
|
||||||
message: @message.errors.full_messages,
|
|
||||||
type: "error"
|
|
||||||
}
|
|
||||||
}, status: :unauthorized
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def destroy
|
|
||||||
authorize @message
|
|
||||||
|
|
||||||
if @message.valid?
|
|
||||||
begin
|
|
||||||
Pusher.trigger(@message.chat_channel.pusher_channels, "message-deleted", @message.to_json)
|
|
||||||
rescue Pusher::Error => e
|
|
||||||
Honeybadger.notify(e)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if @message.destroy
|
|
||||||
render json: { status: "success", message: "Message was deleted" }
|
|
||||||
else
|
|
||||||
render json: {
|
|
||||||
status: "error",
|
|
||||||
message: {
|
|
||||||
chat_channel_id: @message.chat_channel_id,
|
|
||||||
message: @message.errors.full_messages,
|
|
||||||
type: "error"
|
|
||||||
}
|
|
||||||
}, status: :unauthorized
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def update
|
|
||||||
authorize @message
|
|
||||||
|
|
||||||
if @message.update(permitted_attributes(@message).merge(edited_at: Time.zone.now))
|
|
||||||
if @message.valid?
|
|
||||||
begin
|
|
||||||
message_json = create_pusher_payload(@message, "")
|
|
||||||
Pusher.trigger(@message.chat_channel.pusher_channels, "message-edited", message_json)
|
|
||||||
rescue Pusher::Error => e
|
|
||||||
Honeybadger.notify(e)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
render json: { status: "success", message: "Message was edited" }
|
|
||||||
else
|
|
||||||
render json: {
|
|
||||||
status: "error",
|
|
||||||
message: {
|
|
||||||
chat_channel_id: @message.chat_channel_id,
|
|
||||||
message: @message.errors.full_messages,
|
|
||||||
type: "error"
|
|
||||||
}
|
|
||||||
}, status: :unauthorized
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def message_params
|
|
||||||
@mentioned_users_id = params[:message][:mentioned_users_id]
|
|
||||||
params.require(:message).permit(:message_markdown, :user_id, :chat_channel_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_message
|
|
||||||
@message = Message.find(params[:id])
|
|
||||||
end
|
|
||||||
|
|
||||||
def user_not_authorized
|
|
||||||
respond_to do |format|
|
|
||||||
format.json do
|
|
||||||
render json: {
|
|
||||||
status: "error",
|
|
||||||
message: {
|
|
||||||
chat_channel_id: message_params[:chat_channel_id],
|
|
||||||
message: "You can not do that because you are suspended",
|
|
||||||
type: "error"
|
|
||||||
}
|
|
||||||
}, status: :unauthorized
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def notify_mentioned_users(user_ids)
|
|
||||||
# If @all is mentioned then we get an array of all of the channel's users IDs from the channel
|
|
||||||
# https://github.com/forem/forem/blob/9bdef4d4ae0b41612001a62c2409121b654bf71f/app/javascript/chat/chat.jsx#L1562
|
|
||||||
return unless user_ids
|
|
||||||
|
|
||||||
message_json = create_pusher_payload(@message, @temp_message_id)
|
|
||||||
|
|
||||||
user_ids.each do |id|
|
|
||||||
Pusher.trigger(ChatChannel.pm_notifications_channel(id), "mentioned", message_json)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -22,7 +22,6 @@ class ModerationsController < ApplicationController
|
||||||
@articles = articles.includes(:user).to_json(JSON_OPTIONS)
|
@articles = articles.includes(:user).to_json(JSON_OPTIONS)
|
||||||
@tag = Tag.find_by(name: params[:tag]) || not_found if params[:tag].present?
|
@tag = Tag.find_by(name: params[:tag]) || not_found if params[:tag].present?
|
||||||
@current_user_tags = current_user.moderator_for_tags
|
@current_user_tags = current_user.moderator_for_tags
|
||||||
@community_mod_channel = current_user.chat_channels.find_by("channel_name LIKE ?", "Community Mods: Team%")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def article
|
def article
|
||||||
|
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
class PusherController < ApplicationController
|
|
||||||
def auth
|
|
||||||
if valid_channel
|
|
||||||
response = Pusher.authenticate(params[:channel_name], params[:socket_id],
|
|
||||||
user_id: current_user.id) # => required
|
|
||||||
render json: response
|
|
||||||
else
|
|
||||||
render json: { text: "Forbidden", status: "403" }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def valid_channel
|
|
||||||
valid_private_dm_channel || valid_private_group_channel
|
|
||||||
end
|
|
||||||
|
|
||||||
def valid_private_dm_channel
|
|
||||||
current_user && params[:channel_name] == ChatChannel.pm_notifications_channel(current_user.id)
|
|
||||||
end
|
|
||||||
|
|
||||||
def valid_private_group_channel
|
|
||||||
return false unless params[:channel_name].include?("private-channel-")
|
|
||||||
|
|
||||||
id = params[:channel_name].split("-").last
|
|
||||||
channel = ChatChannel.find(id)
|
|
||||||
channel.has_member?(current_user)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,17 +1,8 @@
|
||||||
class SearchController < ApplicationController
|
class SearchController < ApplicationController
|
||||||
before_action :authenticate_user!, only: %i[tags chat_channels reactions usernames]
|
before_action :authenticate_user!, only: %i[tags reactions usernames]
|
||||||
before_action :format_integer_params
|
before_action :format_integer_params
|
||||||
before_action :sanitize_params, only: %i[listings reactions feed_content]
|
before_action :sanitize_params, only: %i[listings reactions feed_content]
|
||||||
|
|
||||||
CHAT_CHANNEL_PARAMS = %i[
|
|
||||||
channel_status
|
|
||||||
channel_type
|
|
||||||
page
|
|
||||||
per_page
|
|
||||||
status
|
|
||||||
user_id
|
|
||||||
].freeze
|
|
||||||
|
|
||||||
LISTINGS_PARAMS = [
|
LISTINGS_PARAMS = [
|
||||||
:category,
|
:category,
|
||||||
:listing_search,
|
:listing_search,
|
||||||
|
|
@ -65,23 +56,6 @@ class SearchController < ApplicationController
|
||||||
render json: { result: result }
|
render json: { result: result }
|
||||||
end
|
end
|
||||||
|
|
||||||
def chat_channels
|
|
||||||
user_ids =
|
|
||||||
if chat_channel_params[:user_id].present?
|
|
||||||
[current_user.id, Settings::General.mascot_user_id, chat_channel_params[:user_id]].reject(&:blank?)
|
|
||||||
else
|
|
||||||
[current_user.id]
|
|
||||||
end
|
|
||||||
|
|
||||||
result = Search::ChatChannelMembership.search_documents(
|
|
||||||
user_ids: user_ids,
|
|
||||||
page: chat_channel_params[:page],
|
|
||||||
per_page: chat_channel_params[:per_page],
|
|
||||||
)
|
|
||||||
|
|
||||||
render json: { result: result }
|
|
||||||
end
|
|
||||||
|
|
||||||
def listings
|
def listings
|
||||||
result = Search::Listing.search_documents(
|
result = Search::Listing.search_documents(
|
||||||
category: listing_params[:category],
|
category: listing_params[:category],
|
||||||
|
|
@ -193,10 +167,6 @@ class SearchController < ApplicationController
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def chat_channel_params
|
|
||||||
params.permit(CHAT_CHANNEL_PARAMS)
|
|
||||||
end
|
|
||||||
|
|
||||||
def listing_params
|
def listing_params
|
||||||
params.permit(LISTINGS_PARAMS)
|
params.permit(LISTINGS_PARAMS)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ class UserBlocksController < ApplicationController
|
||||||
@user_block.config = "default"
|
@user_block.config = "default"
|
||||||
|
|
||||||
if @user_block.save
|
if @user_block.save
|
||||||
UserBlocks::ChannelHandler.new(@user_block).block_chat_channel
|
|
||||||
current_user.stop_following(@user_block.blocked)
|
current_user.stop_following(@user_block.blocked)
|
||||||
@user_block.blocked.stop_following(current_user)
|
@user_block.blocked.stop_following(current_user)
|
||||||
render json: { result: "blocked" }
|
render json: { result: "blocked" }
|
||||||
|
|
@ -39,7 +38,6 @@ class UserBlocksController < ApplicationController
|
||||||
authorize @user_block
|
authorize @user_block
|
||||||
|
|
||||||
if @user_block.destroy
|
if @user_block.destroy
|
||||||
UserBlocks::ChannelHandler.new(@user_block).unblock_chat_channel
|
|
||||||
render json: { result: "unblocked" }
|
render json: { result: "unblocked" }
|
||||||
else
|
else
|
||||||
render json: { error: @user_block.errors_as_sentence, status: 422 }, status: :unprocessable_entity
|
render json: { error: @user_block.errors_as_sentence, status: 422 }, status: :unprocessable_entity
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ module Users
|
||||||
ALLOWED_PARAMS = %i[email_badge_notifications
|
ALLOWED_PARAMS = %i[email_badge_notifications
|
||||||
email_comment_notifications
|
email_comment_notifications
|
||||||
email_community_mod_newsletter
|
email_community_mod_newsletter
|
||||||
email_connect_messages
|
|
||||||
email_digest_periodic
|
email_digest_periodic
|
||||||
email_follower_notifications
|
email_follower_notifications
|
||||||
email_membership_newsletter
|
email_membership_newsletter
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
module ApplicationHelper
|
module ApplicationHelper
|
||||||
# rubocop:disable Performance/OpenStruct
|
# rubocop:disable Style/OpenStructUse, Performance/OpenStruct
|
||||||
USER_COLORS = ["#19063A", "#dce9f3"].freeze
|
USER_COLORS = ["#19063A", "#dce9f3"].freeze
|
||||||
|
|
||||||
DELETED_USER = OpenStruct.new(
|
DELETED_USER = OpenStruct.new(
|
||||||
|
|
@ -11,7 +11,7 @@ module ApplicationHelper
|
||||||
twitter_username: nil,
|
twitter_username: nil,
|
||||||
github_username: nil,
|
github_username: nil,
|
||||||
)
|
)
|
||||||
# rubocop:enable Performance/OpenStruct
|
# rubocop:enable Style/OpenStructUse, Performance/OpenStruct
|
||||||
|
|
||||||
LARGE_USERBASE_THRESHOLD = 1000
|
LARGE_USERBASE_THRESHOLD = 1000
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
module ChatChannelMembershipsHelper
|
|
||||||
def formatted_membership_user(memberships)
|
|
||||||
memberships.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: Images::Profile.call(membership.user.profile_image_url, length: 90),
|
|
||||||
chat_channel_name: membership.chat_channel.channel_name,
|
|
||||||
chat_channel_id: membership.chat_channel.id,
|
|
||||||
slug: membership.chat_channel.slug
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def membership_users(memberships)
|
|
||||||
memberships.includes(:user).map do |membership|
|
|
||||||
format_membership(membership)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def format_membership(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: Images::Profile.call(membership.user.profile_image_url, length: 90)
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
module ChatChannelsHelper
|
|
||||||
def unopened_json_response
|
|
||||||
if session_current_user_id
|
|
||||||
ChatChannelMembership.where(user_id: session_current_user_id)
|
|
||||||
.where(has_unopened_messages: true)
|
|
||||||
.where(show_global_badge_notification: true)
|
|
||||||
.where.not(status: %w[removed_from_channel left_channel])
|
|
||||||
.includes(%i[chat_channel user])
|
|
||||||
.order("chat_channel_memberships.updated_at" => :desc)
|
|
||||||
else
|
|
||||||
ChatChannelMembership.none
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def pending_json_response
|
|
||||||
if current_user
|
|
||||||
current_user
|
|
||||||
.chat_channel_memberships.includes(:chat_channel)
|
|
||||||
.where(status: "pending")
|
|
||||||
.order("chat_channel_memberships.updated_at" => :desc)
|
|
||||||
else
|
|
||||||
ChatChannelMembership.none
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def unopened_ids_response
|
|
||||||
ChatChannelMembership.where(user_id: session_current_user_id).includes(:chat_channel)
|
|
||||||
.where(has_unopened_messages: true).where.not(status: %w[removed_from_channel
|
|
||||||
left_channel]).pluck(:chat_channel_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
def joining_request_json_response
|
|
||||||
requested_memberships_id = current_user
|
|
||||||
.chat_channel_memberships
|
|
||||||
.includes(:chat_channel)
|
|
||||||
.where(chat_channels: { discoverable: true }, role: "mod")
|
|
||||||
.pluck(:chat_channel_id)
|
|
||||||
.flat_map { |membership_id| ChatChannel.find_by(id: membership_id).requested_memberships.ids }
|
|
||||||
|
|
||||||
ChatChannelMembership
|
|
||||||
.includes(%i[user chat_channel])
|
|
||||||
.where(id: requested_memberships_id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
module MessagesHelper
|
|
||||||
def create_pusher_payload(new_message, temp_id)
|
|
||||||
payload = {
|
|
||||||
temp_id: temp_id,
|
|
||||||
id: new_message.id,
|
|
||||||
user_id: new_message.user.id,
|
|
||||||
chat_channel_id: new_message.chat_channel.id,
|
|
||||||
chat_channel_adjusted_slug: new_message.chat_channel.adjusted_slug(current_user, "sender"),
|
|
||||||
channel_type: new_message.chat_channel.channel_type,
|
|
||||||
username: new_message.user.username,
|
|
||||||
profile_image_url: Images::Profile.call(new_message.user.profile_image_url, length: 90),
|
|
||||||
message: new_message.message_html,
|
|
||||||
markdown: new_message.message_markdown,
|
|
||||||
edited_at: new_message.edited_at,
|
|
||||||
timestamp: Time.current,
|
|
||||||
color: new_message.preferred_user_color,
|
|
||||||
reception_method: "pushed",
|
|
||||||
action: new_message.chat_action
|
|
||||||
}
|
|
||||||
|
|
||||||
if new_message.chat_channel.group?
|
|
||||||
payload[:chat_channel_adjusted_slug] = new_message.chat_channel.adjusted_slug
|
|
||||||
end
|
|
||||||
payload.to_json
|
|
||||||
end
|
|
||||||
|
|
||||||
def pusher_message_created(is_single, message, temp_message_id)
|
|
||||||
return unless message.valid?
|
|
||||||
|
|
||||||
begin
|
|
||||||
message_json = create_pusher_payload(message, temp_message_id)
|
|
||||||
if is_single
|
|
||||||
Pusher.trigger(ChatChannel.pm_notifications_channel(message.user_id), "message-created", message_json)
|
|
||||||
else
|
|
||||||
Pusher.trigger(message.chat_channel.pusher_channels, "message-created", message_json)
|
|
||||||
end
|
|
||||||
rescue Pusher::Error => e
|
|
||||||
Honeybadger.notify(e)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -23,6 +23,6 @@ module NotificationsHelper
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
I18n.t(action, user: key_to_link.call("user"))
|
I18n.t(action, user: key_to_link.call("user"))
|
||||||
end.html_safe
|
end.html_safe # rubocop:disable Rails/OutputSafety
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { defaultMembershipPropType } from '../../common-prop-types/membership-prop-type';
|
|
||||||
import { Membership } from './Membership';
|
|
||||||
import { Button } from '@crayons';
|
|
||||||
|
|
||||||
export const ActiveMembershipsSection = ({
|
|
||||||
activeMemberships,
|
|
||||||
removeMembership,
|
|
||||||
currentMembershipRole,
|
|
||||||
toggleScreens,
|
|
||||||
}) => {
|
|
||||||
const activeMembershipList = activeMemberships.slice(0, 4);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-testid="active-memberships"
|
|
||||||
className="p-4 grid gap-2 crayons-card mb-4"
|
|
||||||
data-active-count={activeMemberships ? activeMemberships.length : 0}
|
|
||||||
>
|
|
||||||
<h3 className="mb-2 active_members">Members</h3>
|
|
||||||
{activeMembershipList.map((activeMembership) => (
|
|
||||||
<Membership
|
|
||||||
membership={activeMembership}
|
|
||||||
removeMembership={removeMembership}
|
|
||||||
membershipType="active"
|
|
||||||
currentMembershipRole={currentMembershipRole}
|
|
||||||
className="active-member"
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
<div className="row align-center">
|
|
||||||
<Button
|
|
||||||
className="align-center view-all-memberships"
|
|
||||||
size="s"
|
|
||||||
onClick={toggleScreens}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
View All
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
ActiveMembershipsSection.propTypes = {
|
|
||||||
activeMemberships: PropTypes.arrayOf(defaultMembershipPropType).isRequired,
|
|
||||||
removeMembership: PropTypes.func.isRequired,
|
|
||||||
currentMembershipRole: PropTypes.string.isRequired,
|
|
||||||
toggleScreens: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
export const ChannelDescriptionSection = ({
|
|
||||||
channelName,
|
|
||||||
channelDescription,
|
|
||||||
currentMembershipRole,
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<div className="p-4 grid gap-2 crayons-card mb-4 channel_details">
|
|
||||||
<h2 className="mb-1 channel_title">{channelName}</h2>
|
|
||||||
<p>{channelDescription}</p>
|
|
||||||
<p className="fw-bold">You are a channel {currentMembershipRole}</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
ChannelDescriptionSection.propTypes = {
|
|
||||||
channelName: PropTypes.string.isRequired,
|
|
||||||
currentMembershipRole: PropTypes.string.isRequired,
|
|
||||||
channelDescription: PropTypes.string.isRequired,
|
|
||||||
};
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { defaultMembershipPropType } from '../../common-prop-types/membership-prop-type';
|
|
||||||
import { ActiveMembershipsSection } from './ActiveMembershipsSection';
|
|
||||||
import { PendingMembershipSection } from './PendingMembershipSection';
|
|
||||||
import { RequestedMembershipSection } from './RequestedMembershipSection';
|
|
||||||
|
|
||||||
export const ChatChannelMembershipSection = ({
|
|
||||||
pendingMemberships,
|
|
||||||
requestedMemberships,
|
|
||||||
chatChannelAcceptMembership,
|
|
||||||
activeMemberships,
|
|
||||||
removeMembership,
|
|
||||||
currentMembershipRole,
|
|
||||||
toggleScreens,
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<div className="membership-list">
|
|
||||||
<ActiveMembershipsSection
|
|
||||||
activeMemberships={activeMemberships}
|
|
||||||
removeMembership={removeMembership}
|
|
||||||
currentMembershipRole={currentMembershipRole}
|
|
||||||
toggleScreens={toggleScreens}
|
|
||||||
/>
|
|
||||||
<PendingMembershipSection
|
|
||||||
pendingMemberships={pendingMemberships}
|
|
||||||
removeMembership={removeMembership}
|
|
||||||
currentMembershipRole={currentMembershipRole}
|
|
||||||
/>
|
|
||||||
<RequestedMembershipSection
|
|
||||||
requestedMemberships={requestedMemberships}
|
|
||||||
removeMembership={removeMembership}
|
|
||||||
chatChannelAcceptMembership={chatChannelAcceptMembership}
|
|
||||||
currentMembershipRole={currentMembershipRole}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
ChatChannelMembershipSection.propTypes = {
|
|
||||||
pendingMemberships: PropTypes.arrayOf(defaultMembershipPropType).isRequired,
|
|
||||||
requestedMemberships: PropTypes.arrayOf(defaultMembershipPropType).isRequired,
|
|
||||||
activeMemberships: PropTypes.arrayOf(defaultMembershipPropType).isRequired,
|
|
||||||
removeMembership: PropTypes.func.isRequired,
|
|
||||||
chatChannelAcceptMembership: PropTypes.func.isRequired,
|
|
||||||
currentMembershipRole: PropTypes.string.isRequired,
|
|
||||||
toggleScreens: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
@ -1,440 +0,0 @@
|
||||||
import { h, Component } from 'preact';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import {
|
|
||||||
getChannelDetails,
|
|
||||||
updatePersonalChatChannelNotificationSettings,
|
|
||||||
rejectChatChannelJoiningRequest,
|
|
||||||
acceptChatChannelJoiningRequest,
|
|
||||||
updateChatChannelDescription,
|
|
||||||
sendChatChannelInvitation,
|
|
||||||
leaveChatChannelMembership,
|
|
||||||
updateMembershipRole,
|
|
||||||
} from '../actions/chat_channel_setting_actions';
|
|
||||||
|
|
||||||
import { addSnackbarItem } from '../../Snackbar';
|
|
||||||
import { ManageActiveMembership } from './MembershipManager/ManageActiveMembership';
|
|
||||||
import { ChatChannelSettingsSection } from './ChatChannelSettingsSection';
|
|
||||||
|
|
||||||
export class ChatChannelSettings extends Component {
|
|
||||||
static propTypes = {
|
|
||||||
handleLeavingChannel: PropTypes.func.isRequired,
|
|
||||||
activeMembershipId: PropTypes.number.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
successMessages: null,
|
|
||||||
errorMessages: null,
|
|
||||||
activeMemberships: [],
|
|
||||||
pendingMemberships: [],
|
|
||||||
requestedMemberships: [],
|
|
||||||
chatChannel: null,
|
|
||||||
currentMembership: null,
|
|
||||||
activeMembershipId: null,
|
|
||||||
channelDescription: null,
|
|
||||||
channelDiscoverable: null,
|
|
||||||
invitationUsernames: null,
|
|
||||||
showGlobalBadgeNotification: null,
|
|
||||||
displaySettings: true,
|
|
||||||
displayMembershipManager: false,
|
|
||||||
invitationLink: null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.updateChannelDetails();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillReceiveProps() {
|
|
||||||
const { activeMembershipId } = this.props;
|
|
||||||
this.setState({
|
|
||||||
activeMembershipId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
updateChannelDetails = () => {
|
|
||||||
const { activeMembershipId } = this.props;
|
|
||||||
|
|
||||||
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,
|
|
||||||
invitationLink: result.invitation_link,
|
|
||||||
});
|
|
||||||
} 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,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
handlePersonChannelSetting = (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.updateChannelDetails();
|
|
||||||
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.updateChannelDetails();
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
handleChannelInvitations = async () => {
|
|
||||||
const { invitationUsernames, chatChannel } = this.state;
|
|
||||||
const { id } = chatChannel;
|
|
||||||
const response = await sendChatChannelInvitation(id, invitationUsernames);
|
|
||||||
const { message } = response;
|
|
||||||
if (response.success) {
|
|
||||||
this.updateChannelDetails();
|
|
||||||
this.setState({
|
|
||||||
errorMessages: null,
|
|
||||||
successMessages: response.message,
|
|
||||||
invitationUsernames: null,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.setState({
|
|
||||||
successMessages: null,
|
|
||||||
errorMessages: response.message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
addSnackbarItem({ message });
|
|
||||||
};
|
|
||||||
|
|
||||||
handleleaveChannelMembership = 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);
|
|
||||||
const { message } = response;
|
|
||||||
if (response.success) {
|
|
||||||
this.setState({
|
|
||||||
successMessages: message,
|
|
||||||
errorMessages: null,
|
|
||||||
});
|
|
||||||
this.props.handleLeavingChannel(currentMembership.id);
|
|
||||||
} else {
|
|
||||||
this.setState({
|
|
||||||
successMessages: null,
|
|
||||||
errorMessages: response.message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
addSnackbarItem({ message });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
toggleScreens = () => {
|
|
||||||
const { displaySettings, displayMembershipManager } = this.state;
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
displaySettings: !displaySettings,
|
|
||||||
displayMembershipManager: !displayMembershipManager,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
handleUpdateMembershipRole = async (e) => {
|
|
||||||
const { membershipId, role } = e.target.dataset;
|
|
||||||
const { chatChannel } = this.state;
|
|
||||||
const response = await updateMembershipRole(
|
|
||||||
membershipId,
|
|
||||||
chatChannel.id,
|
|
||||||
role,
|
|
||||||
);
|
|
||||||
const { message } = response;
|
|
||||||
if (response.success) {
|
|
||||||
this.updateChannelDetails();
|
|
||||||
this.setState((prevState) => {
|
|
||||||
const { activeMemberships } = prevState;
|
|
||||||
const updatedActiveMemberships = activeMemberships.map(
|
|
||||||
(activeMembership) => {
|
|
||||||
if (activeMembership.membership_id === Number(membershipId)) {
|
|
||||||
return { ...activeMembership, role };
|
|
||||||
}
|
|
||||||
return activeMembership;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
...prevState,
|
|
||||||
activeMemberships: updatedActiveMemberships,
|
|
||||||
errorMessages: null,
|
|
||||||
successMessages: response.message,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.setState({
|
|
||||||
successMessages: null,
|
|
||||||
errorMessages: response.message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
addSnackbarItem({ message });
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
chatChannel,
|
|
||||||
currentMembership,
|
|
||||||
activeMemberships,
|
|
||||||
pendingMemberships,
|
|
||||||
requestedMemberships,
|
|
||||||
channelDescription,
|
|
||||||
channelDiscoverable,
|
|
||||||
invitationUsernames,
|
|
||||||
showGlobalBadgeNotification,
|
|
||||||
displaySettings,
|
|
||||||
invitationLink,
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
if (!chatChannel) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="activechatchannel__activeArticle channel_settings">
|
|
||||||
<div className="p-4">
|
|
||||||
{displaySettings ? (
|
|
||||||
<ChatChannelSettingsSection
|
|
||||||
channelDiscoverable={channelDiscoverable}
|
|
||||||
updateCurrentMembershipNotificationSettings={
|
|
||||||
this.updateCurrentMembershipNotificationSettings
|
|
||||||
}
|
|
||||||
handleleaveChannelMembership={this.handleleaveChannelMembership}
|
|
||||||
handlePersonChannelSetting={this.handlePersonChannelSetting}
|
|
||||||
handleChannelDescriptionChanges={
|
|
||||||
this.handleChannelDescriptionChanges
|
|
||||||
}
|
|
||||||
handleChannelDiscoverableStatus={
|
|
||||||
this.handleChannelDiscoverableStatus
|
|
||||||
}
|
|
||||||
handleDescriptionChange={this.handleDescriptionChange}
|
|
||||||
handleChannelInvitations={this.handleChannelInvitations}
|
|
||||||
handleInvitationUsernames={this.handleInvitationUsernames}
|
|
||||||
toggleScreens={this.toggleScreens}
|
|
||||||
removeMembership={this.removeMembership}
|
|
||||||
chatChannelAcceptMembership={this.chatChannelAcceptMembership}
|
|
||||||
channelDescription={channelDescription}
|
|
||||||
chatChannel={chatChannel}
|
|
||||||
currentMembership={currentMembership}
|
|
||||||
activeMemberships={activeMemberships}
|
|
||||||
pendingMemberships={pendingMemberships}
|
|
||||||
requestedMemberships={requestedMemberships}
|
|
||||||
invitationUsernames={invitationUsernames}
|
|
||||||
showGlobalBadgeNotification={showGlobalBadgeNotification}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<ManageActiveMembership
|
|
||||||
activeMemberships={activeMemberships}
|
|
||||||
currentMembership={currentMembership}
|
|
||||||
chatChannel={chatChannel}
|
|
||||||
invitationLink={invitationLink}
|
|
||||||
removeMembership={this.removeMembership}
|
|
||||||
handleUpdateMembershipRole={this.handleUpdateMembershipRole}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,103 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { ModSection } from './ModSection';
|
|
||||||
import { PersonalSettings } from './PersonalSetting';
|
|
||||||
import { LeaveMembershipSection } from './LeaveMembershipSection';
|
|
||||||
import { ModFaqSection } from './ModFaqSection';
|
|
||||||
import { ChannelDescriptionSection } from './ChannelDescriptionSection';
|
|
||||||
import { ChatChannelMembershipSection } from './ChatChannelMembershipSection';
|
|
||||||
|
|
||||||
export const ChatChannelSettingsSection = ({
|
|
||||||
channelDiscoverable,
|
|
||||||
updateCurrentMembershipNotificationSettings,
|
|
||||||
handleleaveChannelMembership,
|
|
||||||
handlePersonChannelSetting,
|
|
||||||
handleChannelDescriptionChanges,
|
|
||||||
handleChannelDiscoverableStatus,
|
|
||||||
handleDescriptionChange,
|
|
||||||
handleChannelInvitations,
|
|
||||||
handleInvitationUsernames,
|
|
||||||
toggleScreens,
|
|
||||||
removeMembership,
|
|
||||||
chatChannelAcceptMembership,
|
|
||||||
channelDescription,
|
|
||||||
chatChannel,
|
|
||||||
currentMembership,
|
|
||||||
activeMemberships,
|
|
||||||
pendingMemberships,
|
|
||||||
requestedMemberships,
|
|
||||||
invitationUsernames,
|
|
||||||
showGlobalBadgeNotification,
|
|
||||||
}) => (
|
|
||||||
<div>
|
|
||||||
<ChannelDescriptionSection
|
|
||||||
channelName={chatChannel.name}
|
|
||||||
channelDescription={chatChannel.description}
|
|
||||||
currentMembershipRole={currentMembership.role}
|
|
||||||
className="channel-description-section"
|
|
||||||
/>
|
|
||||||
<ChatChannelMembershipSection
|
|
||||||
currentMembershipRole={currentMembership.role}
|
|
||||||
activeMemberships={activeMemberships}
|
|
||||||
removeMembership={removeMembership}
|
|
||||||
pendingMemberships={pendingMemberships}
|
|
||||||
requestedMemberships={requestedMemberships}
|
|
||||||
chatChannelAcceptMembership={chatChannelAcceptMembership}
|
|
||||||
toggleScreens={toggleScreens}
|
|
||||||
className="channel-membership-sections"
|
|
||||||
/>
|
|
||||||
<ModSection
|
|
||||||
invitationUsernames={invitationUsernames}
|
|
||||||
handleInvitationUsernames={handleInvitationUsernames}
|
|
||||||
handleChannelInvitations={handleChannelInvitations}
|
|
||||||
channelDescription={channelDescription}
|
|
||||||
handleDescriptionChange={handleDescriptionChange}
|
|
||||||
channelDiscoverable={channelDiscoverable}
|
|
||||||
handleChannelDiscoverableStatus={handleChannelDiscoverableStatus}
|
|
||||||
handleChannelDescriptionChanges={handleChannelDescriptionChanges}
|
|
||||||
currentMembershipRole={currentMembership.role}
|
|
||||||
className="channel-mod-section"
|
|
||||||
/>
|
|
||||||
<PersonalSettings
|
|
||||||
updateCurrentMembershipNotificationSettings={
|
|
||||||
updateCurrentMembershipNotificationSettings
|
|
||||||
}
|
|
||||||
showGlobalBadgeNotification={showGlobalBadgeNotification}
|
|
||||||
handlePersonChannelSetting={handlePersonChannelSetting}
|
|
||||||
className="channel-personal-seeting"
|
|
||||||
/>
|
|
||||||
<LeaveMembershipSection
|
|
||||||
currentMembershipRole={currentMembership.role}
|
|
||||||
handleleaveChannelMembership={handleleaveChannelMembership}
|
|
||||||
className="channel-leave-membership-section"
|
|
||||||
/>
|
|
||||||
<ModFaqSection
|
|
||||||
currentMembershipRole={currentMembership.role}
|
|
||||||
className="channel-mod-faq"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
ChatChannelSettingsSection.propTypes = {
|
|
||||||
chatChannel: PropTypes.isRequired,
|
|
||||||
currentMembership: PropTypes.isRequired,
|
|
||||||
activeMemberships: PropTypes.isRequired,
|
|
||||||
pendingMemberships: PropTypes.isRequired,
|
|
||||||
requestedMemberships: PropTypes.isRequired,
|
|
||||||
invitationUsernames: PropTypes.string.isRequired,
|
|
||||||
channelDescription: PropTypes.string.isRequired,
|
|
||||||
channelDiscoverable: PropTypes.bool.isRequired,
|
|
||||||
showGlobalBadgeNotification: PropTypes.bool.isRequired,
|
|
||||||
handleleaveChannelMembership: PropTypes.func.isRequired,
|
|
||||||
chatChannelAcceptMembership: PropTypes.func.isRequired,
|
|
||||||
removeMembership: PropTypes.func.isRequired,
|
|
||||||
toggleScreens: PropTypes.func.isRequired,
|
|
||||||
handleInvitationUsernames: PropTypes.func.isRequired,
|
|
||||||
handleChannelInvitations: PropTypes.func.isRequired,
|
|
||||||
handleDescriptionChange: PropTypes.func.isRequired,
|
|
||||||
handleChannelDiscoverableStatus: PropTypes.func.isRequired,
|
|
||||||
handleChannelDescriptionChanges: PropTypes.func.isRequired,
|
|
||||||
handlePersonChannelSetting: PropTypes.func.isRequired,
|
|
||||||
updateCurrentMembershipNotificationSettings: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { Button } from '@crayons';
|
|
||||||
|
|
||||||
export const InviteForm = ({
|
|
||||||
handleChannelInvitations,
|
|
||||||
invitationUsernames,
|
|
||||||
handleInvitationUsernames,
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<div data-testid="invite-form" className="crayons-card p-4 grid gap-2 mb-4">
|
|
||||||
<div className="crayons-field">
|
|
||||||
<label
|
|
||||||
className="crayons-field__label invitation_form_title"
|
|
||||||
htmlFor="chat_channel_membership_invitation_usernames"
|
|
||||||
>
|
|
||||||
Usernames to invite
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
placeholder="Comma separated"
|
|
||||||
className="crayons-textfield"
|
|
||||||
type="text"
|
|
||||||
value={invitationUsernames}
|
|
||||||
name="chat_channel_membership[invitation_usernames]"
|
|
||||||
id="chat_channel_membership_invitation_usernames"
|
|
||||||
onChange={handleInvitationUsernames}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Button type="submit" onClick={handleChannelInvitations}>
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
InviteForm.propTypes = {
|
|
||||||
handleInvitationUsernames: PropTypes.func.isRequired,
|
|
||||||
handleChannelInvitations: PropTypes.func.isRequired,
|
|
||||||
invitationUsernames: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import PropsType from 'prop-types';
|
|
||||||
import { Button } from '@crayons';
|
|
||||||
|
|
||||||
export const LeaveMembershipSection = ({
|
|
||||||
handleleaveChannelMembership,
|
|
||||||
currentMembershipRole,
|
|
||||||
}) => {
|
|
||||||
if (currentMembershipRole === 'mod') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="crayons-card p-4 grid gap-2 mb-4 leave_membership_section">
|
|
||||||
<h3>Danger Zone</h3>
|
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
className="leave_button"
|
|
||||||
variant="danger"
|
|
||||||
type="submit"
|
|
||||||
onClick={handleleaveChannelMembership}
|
|
||||||
>
|
|
||||||
Leave Channel
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
LeaveMembershipSection.propTypes = {
|
|
||||||
handleleaveChannelMembership: PropsType.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { defaultMembershipPropType } from '../../common-prop-types/membership-prop-type';
|
|
||||||
import { Button } from '@crayons';
|
|
||||||
|
|
||||||
export const Membership = ({
|
|
||||||
membership,
|
|
||||||
removeMembership,
|
|
||||||
membershipType,
|
|
||||||
chatChannelAcceptMembership,
|
|
||||||
currentMembershipRole,
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<div className="flex items-center">
|
|
||||||
<a
|
|
||||||
href={`/${membership.username}`}
|
|
||||||
className="chatmessagebody__username--link"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
data-content="sidecar-user"
|
|
||||||
>
|
|
||||||
<span className="crayons-avatar crayons-avatar--l mr-3">
|
|
||||||
<img
|
|
||||||
className="crayons-avatar__image align-middle"
|
|
||||||
src={membership.image}
|
|
||||||
alt={`${membership.name} profile`}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<span className="mr-2 user_name">{membership.name}</span>
|
|
||||||
</a>
|
|
||||||
{membershipType === 'requested' ? (
|
|
||||||
<Button
|
|
||||||
className="crayons-btn crayons-btn--icon-rounded crayons-btn--ghost add-membership"
|
|
||||||
type="button"
|
|
||||||
onClick={chatChannelAcceptMembership}
|
|
||||||
data-membership-id={membership.membership_id}
|
|
||||||
>
|
|
||||||
+
|
|
||||||
</Button>
|
|
||||||
) : null}
|
|
||||||
{membership.role !== 'mod' && currentMembershipRole === 'mod' ? (
|
|
||||||
<Button
|
|
||||||
className="crayons-btn crayons-btn--icon-rounded crayons-btn--ghost remove-membership"
|
|
||||||
type="button"
|
|
||||||
onClick={removeMembership}
|
|
||||||
data-membership-id={membership.membership_id}
|
|
||||||
data-membership-status={membership.status}
|
|
||||||
>
|
|
||||||
x
|
|
||||||
</Button>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
Membership.propTypes = {
|
|
||||||
membership: PropTypes.objectOf(defaultMembershipPropType).isRequired,
|
|
||||||
removeMembership: PropTypes.func.isRequired,
|
|
||||||
membershipType: PropTypes.func.isRequired,
|
|
||||||
chatChannelAcceptMembership: PropTypes.func.isRequired,
|
|
||||||
currentMembershipRole: PropTypes.string.isRequired,
|
|
||||||
};
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
/* global Runtime */
|
|
||||||
|
|
||||||
import { h, Component } from 'preact';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { Button } from '@crayons';
|
|
||||||
|
|
||||||
const CopyIcon = () => (
|
|
||||||
<svg
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
className="crayons-icon copy-icon"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
role="img"
|
|
||||||
aria-labelledby="fc5f15add1e114844f5e"
|
|
||||||
>
|
|
||||||
<title id="fc5f15add1e114844f5e">Copy Invitation Url</title>
|
|
||||||
<path d="M7 6V3a1 1 0 011-1h12a1 1 0 011 1v14a1 1 0 01-1 1h-3v3c0 .552-.45 1-1.007 1H4.007A1 1 0 013 21l.003-14c0-.552.45-1 1.007-1H7zm2 0h8v10h2V4H9v2zm-2 5v2h6v-2H7zm0 4v2h6v-2H7z" />
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
|
|
||||||
export class InvitationLinkManager extends Component {
|
|
||||||
static propTypes = {
|
|
||||||
invitationLink: PropTypes.string.isRequired,
|
|
||||||
currentMembership: PropTypes.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
invitationLink: props.invitationLink,
|
|
||||||
showImageCopiedMessage: false,
|
|
||||||
currentMembership: props.currentMembership,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
copyText = () => {
|
|
||||||
this.imageMarkdownInput = document.getElementById(
|
|
||||||
'chat-channel-unviation-url',
|
|
||||||
);
|
|
||||||
|
|
||||||
Runtime.copyToClipboard(this.imageMarkdownInput.value).then(() => {
|
|
||||||
this.setState({ showImageCopiedMessage: true });
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
showImageCopiedMessage,
|
|
||||||
invitationLink,
|
|
||||||
currentMembership,
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
if (currentMembership.role !== 'mod') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="p-4 grid gap-2 crayons-card my-4 invitation-section">
|
|
||||||
<h3 className="text-center title">Invitation Link</h3>
|
|
||||||
<clipboard-copy
|
|
||||||
onClick={this.copyText}
|
|
||||||
for="chat-channel-unviation-url"
|
|
||||||
aria-live="polite"
|
|
||||||
className="flex items-center flex-1"
|
|
||||||
aria-controls="image-markdown-copy-link-announcer"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="crayons-textfield mr-2"
|
|
||||||
id="chat-channel-unviation-url"
|
|
||||||
readOnly="true"
|
|
||||||
value={invitationLink}
|
|
||||||
aria-label="invitation-link"
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
className="spec__image-markdown-copy"
|
|
||||||
variant="ghost"
|
|
||||||
contentType="icon"
|
|
||||||
icon={CopyIcon}
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
id="image-markdown-copy-link-announcer"
|
|
||||||
role="alert"
|
|
||||||
className={`fs-s ${showImageCopiedMessage ? '' : 'opacity-0'}`}
|
|
||||||
>
|
|
||||||
Copied!
|
|
||||||
</span>
|
|
||||||
</clipboard-copy>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,123 +0,0 @@
|
||||||
import { h, Component } from 'preact';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { MembershipSection } from './MembershipSection';
|
|
||||||
import { InvitationLinkManager } from './InvitationLinkManager';
|
|
||||||
|
|
||||||
export class ManageActiveMembership extends Component {
|
|
||||||
static 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,
|
|
||||||
currentMembership: PropTypes.isRequired,
|
|
||||||
invitationLink: PropTypes.string.isRequired,
|
|
||||||
removeMembership: PropTypes.func.isRequired,
|
|
||||||
handleUpdateMembershipRole: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
activeMemberships: props.activeMemberships,
|
|
||||||
searchMembers: null,
|
|
||||||
listAllMemberShips: props.activeMemberships,
|
|
||||||
currentMembership: props.currentMembership,
|
|
||||||
invitationLink: props.invitationLink,
|
|
||||||
removeMembership: props.removeMembership,
|
|
||||||
handleUpdateMembershipRole: props.handleUpdateMembershipRole,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillReceiveProps() {
|
|
||||||
const {
|
|
||||||
activeMemberships,
|
|
||||||
currentMembership,
|
|
||||||
removeMembership,
|
|
||||||
invitationLink,
|
|
||||||
} = this.props;
|
|
||||||
this.setState({
|
|
||||||
listAllMemberShips: activeMemberships,
|
|
||||||
currentMembership,
|
|
||||||
invitationLink,
|
|
||||||
removeMembership,
|
|
||||||
activeMemberships,
|
|
||||||
searchMembers: null,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
searchTheMembershipUser = (e) => {
|
|
||||||
const query = e.target?.value?.toLowerCase();
|
|
||||||
|
|
||||||
this.setState((prevState) => {
|
|
||||||
const filteredActiveMemberships = prevState.activeMemberships.filter(
|
|
||||||
(activeMembership) => {
|
|
||||||
const value = activeMembership.name.toLowerCase();
|
|
||||||
return value.includes(query);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
searchMembers: query,
|
|
||||||
listAllMemberShips: filteredActiveMemberships,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
searchMembers,
|
|
||||||
listAllMemberShips,
|
|
||||||
currentMembership,
|
|
||||||
invitationLink,
|
|
||||||
removeMembership,
|
|
||||||
handleUpdateMembershipRole,
|
|
||||||
activeMemberships,
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
const membershipCount = activeMemberships.length;
|
|
||||||
return (
|
|
||||||
<div className="pt-3">
|
|
||||||
<div className="p-4 grid gap-2 crayons-card my-4 mx-auto membership-manager">
|
|
||||||
<h2 className="text-title">Chat Channel Membership manager</h2>
|
|
||||||
</div>
|
|
||||||
<div className="p-4 grid gap-2 crayons-card my-4">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="crayons-textfield"
|
|
||||||
placeholder="Search Member..."
|
|
||||||
value={searchMembers}
|
|
||||||
name="search-members"
|
|
||||||
onKeyUp={this.searchTheMembershipUser.bind(this)}
|
|
||||||
aria-label="search memberships"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
data-spy="scroll"
|
|
||||||
data-offset="5"
|
|
||||||
className="chat_channel-member-list"
|
|
||||||
>
|
|
||||||
<MembershipSection
|
|
||||||
memberships={listAllMemberShips}
|
|
||||||
currentMembership={currentMembership}
|
|
||||||
removeMembership={removeMembership}
|
|
||||||
handleUpdateMembershipRole={handleUpdateMembershipRole}
|
|
||||||
membershipCount={membershipCount}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<InvitationLinkManager
|
|
||||||
invitationLink={invitationLink}
|
|
||||||
currentMembership={currentMembership}
|
|
||||||
className="inviation-link-section"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,129 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import adminEmoji from '../../../../assets/images/twemoji/fire.svg';
|
|
||||||
import { Button } from '@crayons';
|
|
||||||
|
|
||||||
export const Membership = ({
|
|
||||||
membership,
|
|
||||||
currentMembership,
|
|
||||||
removeMembership,
|
|
||||||
handleUpdateMembershipRole,
|
|
||||||
showActionButton,
|
|
||||||
}) => {
|
|
||||||
const addAsModButton =
|
|
||||||
membership.role === 'member' ? (
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
size="s"
|
|
||||||
onClick={handleUpdateMembershipRole}
|
|
||||||
data-membership-id={membership.membership_id}
|
|
||||||
data-role="mod"
|
|
||||||
>
|
|
||||||
Promote to Mod
|
|
||||||
</Button>
|
|
||||||
) : null;
|
|
||||||
|
|
||||||
const addAsMemberButton =
|
|
||||||
membership.role === 'mod' ? (
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
size="s"
|
|
||||||
onClick={handleUpdateMembershipRole}
|
|
||||||
data-membership-id={membership.membership_id}
|
|
||||||
data-role="member"
|
|
||||||
>
|
|
||||||
Remove Mod
|
|
||||||
</Button>
|
|
||||||
) : null;
|
|
||||||
|
|
||||||
const removeMembershipButton =
|
|
||||||
membership.role === 'member' ? (
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
size="s"
|
|
||||||
variant="ghost-danger"
|
|
||||||
onClick={removeMembership}
|
|
||||||
data-membership-id={membership.membership_id}
|
|
||||||
data-membership-status={membership.status}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 32.526 32.526"
|
|
||||||
width="18"
|
|
||||||
height="18"
|
|
||||||
className="crayons-icon"
|
|
||||||
data-membership-id={membership.membership_id}
|
|
||||||
data-membership-status={membership.status}
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill="#4f5458"
|
|
||||||
data-membership-id={membership.membership_id}
|
|
||||||
data-membership-status={membership.status}
|
|
||||||
d="M32.526 2.828L29.698 0 16.263 13.435 2.828 0 0 2.828l13.435 13.435L0 29.698l2.828 2.828 13.435-13.435 13.435 13.435 2.828-2.828-13.435-13.435z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</Button>
|
|
||||||
) : null;
|
|
||||||
|
|
||||||
const dropdown =
|
|
||||||
currentMembership.role === 'mod' && showActionButton ? (
|
|
||||||
<span className="membership-actions">
|
|
||||||
{removeMembershipButton}
|
|
||||||
{addAsModButton}
|
|
||||||
{addAsMemberButton}
|
|
||||||
</span>
|
|
||||||
) : null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex items-center my-3 member-list-item justify-content-between">
|
|
||||||
<div className="">
|
|
||||||
<a
|
|
||||||
href={`/${membership.username}`}
|
|
||||||
className="chatmessagebody__username--link"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
data-content="sidecar-user"
|
|
||||||
>
|
|
||||||
<span className="crayons-avatar crayons-avatar--l mr-3">
|
|
||||||
<img
|
|
||||||
className="crayons-avatar__image align-middle"
|
|
||||||
src={membership.image}
|
|
||||||
alt={`${membership.name} profile`}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<span className="mr-2 user_name">{membership.name}</span>
|
|
||||||
<span>
|
|
||||||
{membership.role === 'mod' ? (
|
|
||||||
<img
|
|
||||||
src={adminEmoji}
|
|
||||||
alt="admin emoji"
|
|
||||||
data-content="admin emoji"
|
|
||||||
className="admin-emoji"
|
|
||||||
title="MOD"
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div className="">{dropdown}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
Membership.propTypes = {
|
|
||||||
membership: PropTypes.objectOf(
|
|
||||||
PropTypes.shape({
|
|
||||||
name: PropTypes.string.isRequired,
|
|
||||||
membership_id: PropTypes.number.isRequired,
|
|
||||||
user_id: PropTypes.number.isRequired,
|
|
||||||
role: PropTypes.string.isRequired,
|
|
||||||
image: PropTypes.string.isRequired,
|
|
||||||
username: PropTypes.string.isRequired,
|
|
||||||
status: PropTypes.string.isRequired,
|
|
||||||
}),
|
|
||||||
).isRequired,
|
|
||||||
currentMembership: PropTypes.isRequired,
|
|
||||||
removeMembership: PropTypes.func.isRequired,
|
|
||||||
handleUpdateMembershipRole: PropTypes.func.isRequired,
|
|
||||||
showActionButton: PropTypes.bool.isRequired,
|
|
||||||
};
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { Membership } from './Membership';
|
|
||||||
|
|
||||||
export const MembershipSection = ({
|
|
||||||
memberships,
|
|
||||||
currentMembership,
|
|
||||||
removeMembership,
|
|
||||||
handleUpdateMembershipRole,
|
|
||||||
membershipCount,
|
|
||||||
}) => {
|
|
||||||
if (!memberships || memberships.length === 0) {
|
|
||||||
return <p className="lh-base">No membership</p>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="membership-section">
|
|
||||||
{memberships.map((activeMembership) => (
|
|
||||||
<Membership
|
|
||||||
membership={activeMembership}
|
|
||||||
membershipType="active"
|
|
||||||
currentMembershipRole={() => {}}
|
|
||||||
className="active-member"
|
|
||||||
currentMembership={currentMembership}
|
|
||||||
removeMembership={removeMembership}
|
|
||||||
handleUpdateMembershipRole={handleUpdateMembershipRole}
|
|
||||||
showActionButton={membershipCount > 1}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
MembershipSection.propType = {
|
|
||||||
memberships: 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,
|
|
||||||
currentMembership: PropTypes.isRequired,
|
|
||||||
removeMembership: PropTypes.func.isRequired,
|
|
||||||
handleUpdateMembershipRole: PropTypes.func.isRequired,
|
|
||||||
membershipCount: PropTypes.number.isRequired,
|
|
||||||
};
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
export const ModFaqSection = ({ currentMembershipRole }) => {
|
|
||||||
if (currentMembershipRole === 'member') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="crayons-card grid gap-2 p-4 faq-section">
|
|
||||||
<p className="contact-details">
|
|
||||||
Questions about Connect moderation?
|
|
||||||
<a
|
|
||||||
href="/contact"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="mx-2 url-link"
|
|
||||||
>
|
|
||||||
Contact site admins
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
ModFaqSection.propTypes = {
|
|
||||||
currentMembershipRole: PropTypes.string.isRequired,
|
|
||||||
};
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { InviteForm } from './InviteForm';
|
|
||||||
import { SettingsForm } from './SettingsForm';
|
|
||||||
|
|
||||||
export const ModSection = ({
|
|
||||||
handleChannelInvitations,
|
|
||||||
invitationUsernames,
|
|
||||||
handleInvitationUsernames,
|
|
||||||
channelDescription,
|
|
||||||
handleDescriptionChange,
|
|
||||||
channelDiscoverable,
|
|
||||||
handleChannelDiscoverableStatus,
|
|
||||||
handleChannelDescriptionChanges,
|
|
||||||
currentMembershipRole,
|
|
||||||
}) => {
|
|
||||||
if (currentMembershipRole === 'member') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="mod-section">
|
|
||||||
<InviteForm
|
|
||||||
handleInvitationUsernames={handleInvitationUsernames}
|
|
||||||
invitationUsernames={invitationUsernames}
|
|
||||||
handleChannelInvitations={handleChannelInvitations}
|
|
||||||
/>
|
|
||||||
<SettingsForm
|
|
||||||
channelDescription={channelDescription}
|
|
||||||
handleDescriptionChange={handleDescriptionChange}
|
|
||||||
channelDiscoverable={channelDiscoverable}
|
|
||||||
handleChannelDiscoverableStatus={handleChannelDiscoverableStatus}
|
|
||||||
handleChannelDescriptionChanges={handleChannelDescriptionChanges}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
ModSection.propTypes = {
|
|
||||||
handleInvitationUsernames: PropTypes.func.isRequired,
|
|
||||||
handleChannelInvitations: 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,
|
|
||||||
};
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { defaultMembershipPropType } from '../../common-prop-types/membership-prop-type';
|
|
||||||
import { Membership } from './Membership';
|
|
||||||
|
|
||||||
export const PendingMembershipSection = ({
|
|
||||||
pendingMemberships,
|
|
||||||
removeMembership,
|
|
||||||
currentMembershipRole,
|
|
||||||
}) => {
|
|
||||||
if (currentMembershipRole === 'member') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-testid="pending-memberships"
|
|
||||||
className="p-4 grid gap-2 crayons-card mb-4 pending_memberships"
|
|
||||||
data-pending-count={pendingMemberships ? pendingMemberships.length : 0}
|
|
||||||
>
|
|
||||||
<h3 className="mb-2">Pending Invitations</h3>
|
|
||||||
{pendingMemberships && pendingMemberships.length > 0
|
|
||||||
? pendingMemberships.map((pendingMembership) => (
|
|
||||||
<Membership
|
|
||||||
membership={pendingMembership}
|
|
||||||
removeMembership={removeMembership}
|
|
||||||
membershipType="pending"
|
|
||||||
currentMembershipRole={currentMembershipRole}
|
|
||||||
className="pending-member"
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
: null}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
PendingMembershipSection.propTypes = {
|
|
||||||
pendingMemberships: PropTypes.arrayOf(defaultMembershipPropType).isRequired,
|
|
||||||
removeMembership: PropTypes.func.isRequired,
|
|
||||||
currentMembershipRole: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { Button } from '@crayons';
|
|
||||||
|
|
||||||
export const PersonalSettings = ({
|
|
||||||
handlePersonChannelSetting,
|
|
||||||
showGlobalBadgeNotification,
|
|
||||||
updateCurrentMembershipNotificationSettings,
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<div className="crayons-card p-4 grid gap-2 mb-4 personl-settings">
|
|
||||||
<h3>Personal Settings</h3>
|
|
||||||
<h4>Notifications</h4>
|
|
||||||
<div className="crayons-field crayons-field--checkbox">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="c3"
|
|
||||||
className="crayons-checkbox"
|
|
||||||
checked={showGlobalBadgeNotification}
|
|
||||||
onChange={handlePersonChannelSetting}
|
|
||||||
/>
|
|
||||||
<label htmlFor="c3" className="crayons-field__label">
|
|
||||||
Receive Notifications for New Messages
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
onClick={updateCurrentMembershipNotificationSettings}
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
PersonalSettings.propTypes = {
|
|
||||||
updateCurrentMembershipNotificationSettings: PropTypes.func.isRequired,
|
|
||||||
showGlobalBadgeNotification: PropTypes.bool.isRequired,
|
|
||||||
handlePersonChannelSetting: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { defaultMembershipPropType } from '../../common-prop-types/membership-prop-type';
|
|
||||||
|
|
||||||
import { Membership } from './Membership';
|
|
||||||
|
|
||||||
export const RequestedMembershipSection = ({
|
|
||||||
requestedMemberships,
|
|
||||||
removeMembership,
|
|
||||||
chatChannelAcceptMembership,
|
|
||||||
currentMembershipRole,
|
|
||||||
}) => {
|
|
||||||
if (currentMembershipRole === 'member') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-testid="requested-memberships"
|
|
||||||
className="p-4 grid gap-2 crayons-card mb-4"
|
|
||||||
data-requested-count={
|
|
||||||
requestedMemberships ? requestedMemberships.length : 0
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<h3 className="mb-2 requested_memberships">Joining Request</h3>
|
|
||||||
{requestedMemberships && requestedMemberships.length > 0
|
|
||||||
? requestedMemberships.map((pendingMembership) => (
|
|
||||||
<Membership
|
|
||||||
membership={pendingMembership}
|
|
||||||
removeMembership={removeMembership}
|
|
||||||
chatChannelAcceptMembership={chatChannelAcceptMembership}
|
|
||||||
membershipType="requested"
|
|
||||||
currentMembershipRole={currentMembershipRole}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
: null}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
RequestedMembershipSection.propTypes = {
|
|
||||||
requestedMemberships: PropTypes.arrayOf(defaultMembershipPropType).isRequired,
|
|
||||||
removeMembership: PropTypes.func.isRequired,
|
|
||||||
chatChannelAcceptMembership: PropTypes.func.isRequired,
|
|
||||||
currentMembershipRole: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { Button } from '@crayons';
|
|
||||||
|
|
||||||
export const SettingsForm = ({
|
|
||||||
channelDescription,
|
|
||||||
handleDescriptionChange,
|
|
||||||
channelDiscoverable,
|
|
||||||
handleChannelDiscoverableStatus,
|
|
||||||
handleChannelDescriptionChanges,
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-testid="settings-form"
|
|
||||||
className="crayons-card p-4 grid gap-2 mb-4 settings-section"
|
|
||||||
>
|
|
||||||
<h3>Channel Settings</h3>
|
|
||||||
<div className="crayons-field">
|
|
||||||
<label
|
|
||||||
className="crayons-field__label"
|
|
||||||
htmlFor="chat_channel_description"
|
|
||||||
>
|
|
||||||
Description
|
|
||||||
</label>
|
|
||||||
<textarea
|
|
||||||
className="crayons-textfield"
|
|
||||||
name="description"
|
|
||||||
id="chat_channel_description"
|
|
||||||
value={channelDescription}
|
|
||||||
onChange={handleDescriptionChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="crayons-field crayons-field--checkbox">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="c2"
|
|
||||||
className="crayons-checkbox"
|
|
||||||
checked={channelDiscoverable}
|
|
||||||
onChange={handleChannelDiscoverableStatus}
|
|
||||||
/>
|
|
||||||
<label htmlFor="c2" className="crayons-field__label">
|
|
||||||
Channel Discoverable
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Button type="submit" onClick={handleChannelDescriptionChanges}>
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
SettingsForm.propTypes = {
|
|
||||||
channelDescription: PropTypes.string.isRequired,
|
|
||||||
handleDescriptionChange: PropTypes.func.isRequired,
|
|
||||||
handleChannelDiscoverableStatus: PropTypes.func.isRequired,
|
|
||||||
handleChannelDescriptionChanges: PropTypes.func.isRequired,
|
|
||||||
channelDiscoverable: PropTypes.bool.isRequired,
|
|
||||||
};
|
|
||||||
|
|
@ -1,147 +0,0 @@
|
||||||
import { h, Fragment } from 'preact';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { useState } from 'preact/hooks';
|
|
||||||
import { reportAbuse, blockUser } from '../actions/requestActions';
|
|
||||||
import { addSnackbarItem } from '../../Snackbar';
|
|
||||||
import { Button, FormField, RadioButton } from '@crayons';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This component render the report abuse
|
|
||||||
*
|
|
||||||
* @param {object} props
|
|
||||||
* @param {object} props.data
|
|
||||||
* @param {function} props.closeReportAbuseForm
|
|
||||||
*
|
|
||||||
* @component
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
*
|
|
||||||
* <ReportAbuse
|
|
||||||
* data={data}
|
|
||||||
* closeReportAbuseForm={closeReportAbuseForm}
|
|
||||||
* />
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export function ReportAbuse({ data, closeReportAbuseForm }) {
|
|
||||||
const [category, setCategory] = useState(null);
|
|
||||||
|
|
||||||
const handleChange = (e) => {
|
|
||||||
setCategory(e.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
const response = await reportAbuse(
|
|
||||||
data.message,
|
|
||||||
'connect',
|
|
||||||
category,
|
|
||||||
data.user_id,
|
|
||||||
);
|
|
||||||
const { success, message } = response;
|
|
||||||
if (success) {
|
|
||||||
const confirmBlock = window.confirm(
|
|
||||||
`The message will be reported.\n\nWould you like to block this person as well?\n\nThis will:
|
|
||||||
- prevent them from commenting on your posts
|
|
||||||
- block all notifications from them
|
|
||||||
- prevent them from messaging you via chat`,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (confirmBlock) {
|
|
||||||
const response = await blockUser(data.user_id);
|
|
||||||
if (response.result === 'blocked') {
|
|
||||||
addSnackbarItem({
|
|
||||||
message:
|
|
||||||
'Your report has been submitted and the user has been blocked',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
addSnackbarItem({ message: 'Your report has been submitted.' });
|
|
||||||
}
|
|
||||||
closeReportAbuseForm();
|
|
||||||
} else {
|
|
||||||
addSnackbarItem({ message });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
<section className="mt-7 p-4 grid gap-2 crayons-card mb-4">
|
|
||||||
<h1 className="lh-tight mb-4 mt-0">Report Abuse</h1>
|
|
||||||
<p>
|
|
||||||
Thank you for reporting any abuse that violates our{' '}
|
|
||||||
<a href="/code-of-conduct">code of conduct</a> or{' '}
|
|
||||||
<a href="/terms">terms and conditions</a>. We continue to try to make
|
|
||||||
this environment a great one for everybody.
|
|
||||||
</p>
|
|
||||||
<fieldset className="report__abuse-options p-4 justify-between">
|
|
||||||
<legend>Why is this content inappropriate?</legend>
|
|
||||||
<FormField variant="radio">
|
|
||||||
<RadioButton
|
|
||||||
id="rude_or_vulgar"
|
|
||||||
name="rude_or_vulgar"
|
|
||||||
value="rude or vulgar"
|
|
||||||
checked={category === 'rude or vulgar'}
|
|
||||||
onClick={handleChange}
|
|
||||||
/>
|
|
||||||
<label htmlFor="rude_or_vulgar" className="crayons-field__label">
|
|
||||||
Rude or vulgar
|
|
||||||
</label>
|
|
||||||
</FormField>
|
|
||||||
<FormField variant="radio">
|
|
||||||
<RadioButton
|
|
||||||
id="harassment"
|
|
||||||
name="harassment"
|
|
||||||
value="harassment"
|
|
||||||
checked={category === 'harassment'}
|
|
||||||
onClick={handleChange}
|
|
||||||
/>
|
|
||||||
<label htmlFor="harassment" className="crayons-field__label">
|
|
||||||
Harassment or hate speech
|
|
||||||
</label>
|
|
||||||
</FormField>
|
|
||||||
<FormField variant="radio">
|
|
||||||
<RadioButton
|
|
||||||
id="spam"
|
|
||||||
name="spam"
|
|
||||||
value="spam"
|
|
||||||
checked={category === 'spam'}
|
|
||||||
onClick={handleChange}
|
|
||||||
/>
|
|
||||||
<label htmlFor="spam" className="crayons-field__label">
|
|
||||||
Spam or copyright issue
|
|
||||||
</label>
|
|
||||||
</FormField>
|
|
||||||
<FormField variant="radio">
|
|
||||||
<RadioButton
|
|
||||||
id="listings"
|
|
||||||
name="listings"
|
|
||||||
value="listings"
|
|
||||||
checked={category === 'listings'}
|
|
||||||
onClick={handleChange}
|
|
||||||
/>
|
|
||||||
<label htmlFor="listings" className="crayons-field__label">
|
|
||||||
Inappropriate listings message/category
|
|
||||||
</label>
|
|
||||||
</FormField>
|
|
||||||
<h2>Message to Report</h2>
|
|
||||||
<div
|
|
||||||
className="reported__message p-2 mt-2 mb-3"
|
|
||||||
// eslint-disable-next-line react/no-danger
|
|
||||||
dangerouslySetInnerHTML={{ __html: data.message }}
|
|
||||||
/>
|
|
||||||
<Button disabled={category === null} size="s" onClick={handleSubmit}>
|
|
||||||
Report Message
|
|
||||||
</Button>
|
|
||||||
</fieldset>
|
|
||||||
</section>
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ReportAbuse.propTypes = {
|
|
||||||
resource: PropTypes.shape({
|
|
||||||
data: PropTypes.shape({
|
|
||||||
user_id: PropTypes.number.isRequired,
|
|
||||||
message: PropTypes.element.isRequired,
|
|
||||||
}),
|
|
||||||
}).isRequired,
|
|
||||||
};
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { RequestListItem } from './RequestListItem';
|
|
||||||
|
|
||||||
export const ChannelRequestSection = ({
|
|
||||||
channelRequests,
|
|
||||||
handleRequestApproval,
|
|
||||||
handleRequestRejection,
|
|
||||||
}) => {
|
|
||||||
if (channelRequests.length < 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-testid="chat-channel-joining-request"
|
|
||||||
data-active-count={channelRequests ? channelRequests.length : 0}
|
|
||||||
>
|
|
||||||
{channelRequests &&
|
|
||||||
channelRequests.map((channelPendingRequest) => {
|
|
||||||
return (
|
|
||||||
<RequestListItem
|
|
||||||
request={channelPendingRequest}
|
|
||||||
handleRequestApproval={handleRequestApproval}
|
|
||||||
handleRequestRejection={handleRequestRejection}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
ChannelRequestSection.propTypes = {
|
|
||||||
channelRequests: 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,
|
|
||||||
channel_name: PropTypes.string.isRequired,
|
|
||||||
}),
|
|
||||||
).isRequired,
|
|
||||||
handleRequestApproval: PropTypes.func.isRequired,
|
|
||||||
handleRequestRejection: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
|
|
||||||
export const HeaderSection = ({}) => (
|
|
||||||
<div className="request_manager_header crayons-card mb-6 grid grid-flow-row gap-6 p-6">
|
|
||||||
<h1>
|
|
||||||
Request Center{' '}
|
|
||||||
<span role="img" aria-label="handshake">
|
|
||||||
🤝
|
|
||||||
</span>
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { Button } from '@crayons';
|
|
||||||
|
|
||||||
export const PendingInvitationListItem = ({ request, updateMembership }) => (
|
|
||||||
<div className="crayons-card mb-6">
|
|
||||||
<div className="crayons-card__body channel-request-card">
|
|
||||||
<div className="request-message d-flex flex-wrap">
|
|
||||||
You got invitation to join <b>{request.chat_channel_name}</b>.
|
|
||||||
</div>
|
|
||||||
<div className="request-actions">
|
|
||||||
<Button
|
|
||||||
className="m-2"
|
|
||||||
size="s"
|
|
||||||
variant="danger"
|
|
||||||
onClick={updateMembership}
|
|
||||||
data-channel-id={request.chat_channel_id}
|
|
||||||
data-membership-id={request.membership_id}
|
|
||||||
data-channel-slug={request.slug}
|
|
||||||
data-user-action="reject"
|
|
||||||
>
|
|
||||||
{' '}
|
|
||||||
Reject
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
className="m-2"
|
|
||||||
size="s"
|
|
||||||
onClick={updateMembership}
|
|
||||||
data-channel-id={request.chat_channel_id}
|
|
||||||
data-membership-id={request.membership_id}
|
|
||||||
data-channel-slug={request.slug}
|
|
||||||
data-user-action="accept"
|
|
||||||
>
|
|
||||||
{' '}
|
|
||||||
Accept
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
PendingInvitationListItem.propTypes = {
|
|
||||||
request: 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,
|
|
||||||
chat_channel_name: PropTypes.string.isRequired,
|
|
||||||
}),
|
|
||||||
).isRequired,
|
|
||||||
updateMembership: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { PendingInvitationListItem } from './PersonalInvitationListItem';
|
|
||||||
|
|
||||||
export const PersonalInvitationSection = ({
|
|
||||||
userInvitations,
|
|
||||||
updateMembership,
|
|
||||||
}) => {
|
|
||||||
if (!userInvitations || userInvitations?.length < 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-testid="user-invitations"
|
|
||||||
data-active-count={userInvitations ? userInvitations.length : 0}
|
|
||||||
>
|
|
||||||
{userInvitations &&
|
|
||||||
userInvitations.map((userInvitation) => {
|
|
||||||
return (
|
|
||||||
<PendingInvitationListItem
|
|
||||||
request={userInvitation}
|
|
||||||
updateMembership={updateMembership}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
PersonalInvitationSection.propTypes = {
|
|
||||||
userInvitations: 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,
|
|
||||||
channel_name: PropTypes.string.isRequired,
|
|
||||||
}),
|
|
||||||
).isRequired,
|
|
||||||
updateMembership: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { Button } from '@crayons';
|
|
||||||
|
|
||||||
export const RequestListItem = ({
|
|
||||||
request,
|
|
||||||
handleRequestRejection,
|
|
||||||
handleRequestApproval,
|
|
||||||
}) => (
|
|
||||||
<div className="crayons-card mb-6">
|
|
||||||
<div className="crayons-card__body channel-request-card">
|
|
||||||
<div className="request-message d-flex flex-wrap">
|
|
||||||
<b>{request.name}</b> requested to join{' '}
|
|
||||||
<b>{request.chat_channel_name}</b>
|
|
||||||
</div>
|
|
||||||
<div className="request-actions">
|
|
||||||
<Button
|
|
||||||
className="m-2"
|
|
||||||
variant="danger"
|
|
||||||
size="s"
|
|
||||||
onClick={handleRequestRejection}
|
|
||||||
data-channel-id={request.chat_channel_id}
|
|
||||||
data-membership-id={request.membership_id}
|
|
||||||
>
|
|
||||||
Reject
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
className="m-2"
|
|
||||||
size="s"
|
|
||||||
onClick={handleRequestApproval}
|
|
||||||
data-channel-id={request.chat_channel_id}
|
|
||||||
data-membership-id={request.membership_id}
|
|
||||||
>
|
|
||||||
Accept
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
RequestListItem.propTypes = {
|
|
||||||
request: 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,
|
|
||||||
chat_channel_name: PropTypes.string.isRequired,
|
|
||||||
}),
|
|
||||||
).isRequired,
|
|
||||||
handleRequestRejection: PropTypes.func.isRequired,
|
|
||||||
handleRequestApproval: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
@ -1,152 +0,0 @@
|
||||||
import { h, Component } from 'preact';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { addSnackbarItem } from '../../Snackbar';
|
|
||||||
import {
|
|
||||||
getChannelRequestInfo,
|
|
||||||
updateMembership,
|
|
||||||
acceptJoiningRequest,
|
|
||||||
rejectJoiningRequest,
|
|
||||||
} from '../actions/requestActions';
|
|
||||||
import { HeaderSection } from './HeaderSection';
|
|
||||||
import { ChannelRequestSection } from './ChannelRequestSection';
|
|
||||||
import { PersonalInvitationSection } from './PersonalInvitationSection';
|
|
||||||
|
|
||||||
export class RequestManager extends Component {
|
|
||||||
static propTypes = {
|
|
||||||
resource: PropTypes.shape({
|
|
||||||
data: PropTypes.object,
|
|
||||||
}).isRequired,
|
|
||||||
updateRequestCount: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
updateRequestCount: props.updateRequestCount,
|
|
||||||
channelJoiningRequests: [],
|
|
||||||
userInvitations: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillReceiveProps() {
|
|
||||||
this.setState({
|
|
||||||
updateRequestCount: this.props.updateRequestCount,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
getChannelRequestInfo().then((response) => {
|
|
||||||
const { result } = response;
|
|
||||||
const { user_joining_requests, channel_joining_memberships } = result;
|
|
||||||
this.setState({
|
|
||||||
channelJoiningRequests: channel_joining_memberships,
|
|
||||||
userInvitations: user_joining_requests,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleIUpdateMembership = async (e) => {
|
|
||||||
const {
|
|
||||||
membershipId,
|
|
||||||
userAction,
|
|
||||||
channelSlug,
|
|
||||||
channelId,
|
|
||||||
} = e.target.dataset;
|
|
||||||
const response = await updateMembership(membershipId, userAction);
|
|
||||||
const { success, membership, message } = response;
|
|
||||||
const { updateRequestCount } = this.state;
|
|
||||||
if (success) {
|
|
||||||
this.setState((prevState) => {
|
|
||||||
const filteredUserInvitations = prevState.userInvitations.filter(
|
|
||||||
(userInvitation) =>
|
|
||||||
userInvitation.membership_id !== membership.membership_id,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
userInvitations: filteredUserInvitations,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
if (userAction === 'accept') {
|
|
||||||
updateRequestCount(true, { channelSlug, channelId });
|
|
||||||
}
|
|
||||||
updateRequestCount();
|
|
||||||
addSnackbarItem({ message });
|
|
||||||
} else {
|
|
||||||
addSnackbarItem({ message });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleAcceptJoingRequest = async (e) => {
|
|
||||||
const { membershipId, channelId } = e.target.dataset;
|
|
||||||
const response = await acceptJoiningRequest(channelId, membershipId);
|
|
||||||
const { success, message, membership } = response;
|
|
||||||
const { updateRequestCount } = this.state;
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
this.setState((prevState) => {
|
|
||||||
const formattedChannelJoiningRequests = prevState.channelJoiningRequests.filter(
|
|
||||||
(channelJoiningRequest) =>
|
|
||||||
channelJoiningRequest.membership_id !== membership.membership_id,
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
channelJoiningRequests: formattedChannelJoiningRequests,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
updateRequestCount();
|
|
||||||
addSnackbarItem({ message });
|
|
||||||
} else {
|
|
||||||
addSnackbarItem({ message });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleRejectJoingRequest = async (e) => {
|
|
||||||
const { membershipId, channelId } = e.target.dataset;
|
|
||||||
const response = await rejectJoiningRequest(channelId, membershipId);
|
|
||||||
const { success, message } = response;
|
|
||||||
const { updateRequestCount } = this.state;
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
this.setState((prevState) => {
|
|
||||||
const formattedChannelJoiningRequests = prevState.channelJoiningRequests.filter(
|
|
||||||
(channelJoiningRequest) =>
|
|
||||||
channelJoiningRequest.membership_id !== Number(membershipId),
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
channelJoiningRequests: formattedChannelJoiningRequests,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
updateRequestCount();
|
|
||||||
addSnackbarItem({ message });
|
|
||||||
} else {
|
|
||||||
addSnackbarItem({ message });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { channelJoiningRequests, userInvitations } = this.state;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div className="p-4">
|
|
||||||
<HeaderSection />
|
|
||||||
{channelJoiningRequests.length <= 0 && userInvitations.length <= 0 ? (
|
|
||||||
<p>You have no pending invitations.</p>
|
|
||||||
) : null}
|
|
||||||
<ChannelRequestSection
|
|
||||||
channelRequests={channelJoiningRequests}
|
|
||||||
handleRequestRejection={this.handleRejectJoingRequest}
|
|
||||||
handleRequestApproval={this.handleAcceptJoingRequest}
|
|
||||||
/>
|
|
||||||
<PersonalInvitationSection
|
|
||||||
userInvitations={userInvitations}
|
|
||||||
updateMembership={this.handleIUpdateMembership}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import { render } from '@testing-library/preact';
|
|
||||||
import { axe } from 'jest-axe';
|
|
||||||
import { ManageActiveMembership } from '../ChatChannelSettings/MembershipManager/ManageActiveMembership';
|
|
||||||
|
|
||||||
const currentModMembership = {
|
|
||||||
name: 'dummy user',
|
|
||||||
username: 'dummyuser',
|
|
||||||
user_id: 1,
|
|
||||||
chat_channel_id: 2,
|
|
||||||
status: 'active',
|
|
||||||
role: 'mod',
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('<ManageActiveMembership />', () => {
|
|
||||||
it('should have no a11y violations', async () => {
|
|
||||||
const { container } = render(
|
|
||||||
<ManageActiveMembership
|
|
||||||
invitationLink="https://dummy-invitation.link"
|
|
||||||
currentMembership={currentModMembership}
|
|
||||||
activeMemberships={[]}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render', () => {
|
|
||||||
const { queryByText, queryByPlaceholderText } = render(
|
|
||||||
<ManageActiveMembership
|
|
||||||
invitationLink="https://dummy-invitation.link"
|
|
||||||
currentMembership={currentModMembership}
|
|
||||||
activeMemberships={[]}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(queryByText('Chat Channel Membership manager')).toBeDefined();
|
|
||||||
expect(queryByPlaceholderText('Search Member...')).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import { render } from '@testing-library/preact';
|
|
||||||
import { axe } from 'jest-axe';
|
|
||||||
import { Membership } from '../ChatChannelSettings/MembershipManager/Membership';
|
|
||||||
|
|
||||||
const membershipData = {
|
|
||||||
name: 'dummy Name',
|
|
||||||
user_id: 1,
|
|
||||||
chat_channel_id: 2,
|
|
||||||
membership_id: 1,
|
|
||||||
username: 'dummyuser',
|
|
||||||
};
|
|
||||||
|
|
||||||
const currentModMembership = {
|
|
||||||
name: 'dummy user',
|
|
||||||
username: 'dummyuser',
|
|
||||||
user_id: 1,
|
|
||||||
chat_channel_id: 2,
|
|
||||||
status: 'active',
|
|
||||||
role: 'mod',
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('<Membership />', () => {
|
|
||||||
it('should have no a11y violations', async () => {
|
|
||||||
const { container } = render(
|
|
||||||
<Membership
|
|
||||||
currentMembership={currentModMembership}
|
|
||||||
membership={membershipData}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render', () => {
|
|
||||||
const { queryByText } = render(
|
|
||||||
<Membership
|
|
||||||
currentMembership={currentModMembership}
|
|
||||||
membership={membershipData}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(queryByText('dummy user')).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import { render } from '@testing-library/preact';
|
|
||||||
import { axe } from 'jest-axe';
|
|
||||||
import { PersonalInvitationSection } from '../RequestManager/PersonalInvitationSection';
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
request: {
|
|
||||||
name: 'Demo name',
|
|
||||||
membership_id: 11,
|
|
||||||
user_id: 10,
|
|
||||||
role: 'member',
|
|
||||||
image: '/image',
|
|
||||||
username: 'demousername',
|
|
||||||
status: 'pending',
|
|
||||||
chat_channel_name: 'demo channel',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('<PersonalInvitationSection />', () => {
|
|
||||||
it('should have no a11y violations', async () => {
|
|
||||||
const { container } = render(
|
|
||||||
<PersonalInvitationSection request={data.request} />,
|
|
||||||
);
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render the the component', () => {
|
|
||||||
const { queryByText } = render(
|
|
||||||
<PersonalInvitationSection request={data.request} />,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
queryByText(
|
|
||||||
`${data.request.name} wants to join ${data.request.chat_channel_name}`,
|
|
||||||
),
|
|
||||||
).toBeDefined();
|
|
||||||
|
|
||||||
expect(queryByText('Reject', { selector: 'button' })).toBeDefined();
|
|
||||||
|
|
||||||
expect(queryByText('Accept', { selector: 'button' })).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import { render } from '@testing-library/preact';
|
|
||||||
import { axe } from 'jest-axe';
|
|
||||||
import { PersonalInvitationSection } from '../RequestManager/PersonalInvitationSection';
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
userInvitations: [
|
|
||||||
{
|
|
||||||
name: 'Demo name',
|
|
||||||
membership_id: 11,
|
|
||||||
user_id: 10,
|
|
||||||
role: 'member',
|
|
||||||
image: '/image',
|
|
||||||
username: 'demousername',
|
|
||||||
status: 'joining_request',
|
|
||||||
chat_channel_name: 'demo channel',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
noUserInvitations: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('<PersonalInvitationSection />', () => {
|
|
||||||
it('should have no a11y violations', async () => {
|
|
||||||
const { container } = render(
|
|
||||||
<PersonalInvitationSection userInvitations={data.userInvitations} />,
|
|
||||||
);
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render the the component', () => {
|
|
||||||
const { getByTestId } = render(
|
|
||||||
<PersonalInvitationSection userInvitations={data.userInvitations} />,
|
|
||||||
);
|
|
||||||
|
|
||||||
const userInvitationsWrapper = getByTestId('user-invitations');
|
|
||||||
expect(
|
|
||||||
Number(userInvitationsWrapper.dataset.activeCount),
|
|
||||||
).toBeGreaterThanOrEqual(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import { render } from '@testing-library/preact';
|
|
||||||
import { axe } from 'jest-axe';
|
|
||||||
import { RequestListItem } from '../RequestManager/RequestListItem';
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
request: {
|
|
||||||
name: 'Demo name',
|
|
||||||
membership_id: 11,
|
|
||||||
user_id: 10,
|
|
||||||
role: 'member',
|
|
||||||
image: '/image',
|
|
||||||
username: 'demousername',
|
|
||||||
status: 'joining_request',
|
|
||||||
chat_channel_name: 'demo channel',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('<RequestListItem />', () => {
|
|
||||||
it('should have no a11y violations', async () => {
|
|
||||||
const { container } = render(<RequestListItem request={data.request} />);
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render the the component', () => {
|
|
||||||
const { queryByText } = render(<RequestListItem request={data.request} />);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
queryByText(
|
|
||||||
`${data.request.name} wants to join ${data.request.chat_channel_name}`,
|
|
||||||
),
|
|
||||||
).toBeDefined();
|
|
||||||
|
|
||||||
expect(queryByText('Reject', { selector: 'button' })).toBeDefined();
|
|
||||||
|
|
||||||
expect(queryByText('Accept', { selector: 'button' })).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,111 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import { render } from '@testing-library/preact';
|
|
||||||
import { axe } from 'jest-axe';
|
|
||||||
import { ActiveMembershipsSection } from '../ChatChannelSettings/ActiveMembershipsSection';
|
|
||||||
|
|
||||||
function getEmptyMembershipData() {
|
|
||||||
return {
|
|
||||||
activeMemberships: [],
|
|
||||||
currentMembershipRole: 'mod',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMembershipData() {
|
|
||||||
return {
|
|
||||||
activeMemberships: [
|
|
||||||
{
|
|
||||||
name: 'test user',
|
|
||||||
username: 'testusername',
|
|
||||||
user_id: '1',
|
|
||||||
membership_id: '2',
|
|
||||||
role: 'mod',
|
|
||||||
status: 'active',
|
|
||||||
image: '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
membershipType: 'active',
|
|
||||||
currentMembershipRole: 'mod',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('<ActiveMembershipsSection />', () => {
|
|
||||||
it('should have no a11y violations when there are no members', async () => {
|
|
||||||
const {
|
|
||||||
activeMemberships,
|
|
||||||
currentMembershipRole,
|
|
||||||
} = getEmptyMembershipData();
|
|
||||||
const { container } = render(
|
|
||||||
<ActiveMembershipsSection
|
|
||||||
activeMemberships={activeMemberships}
|
|
||||||
currentMembershipRole={currentMembershipRole}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have no a11y violations when there are members', async () => {
|
|
||||||
const { activeMemberships, currentMembershipRole } = getMembershipData();
|
|
||||||
const { container } = render(
|
|
||||||
<ActiveMembershipsSection
|
|
||||||
activeMemberships={activeMemberships}
|
|
||||||
currentMembershipRole={currentMembershipRole}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have a title', () => {
|
|
||||||
const {
|
|
||||||
activeMemberships,
|
|
||||||
currentMembershipRole,
|
|
||||||
} = getEmptyMembershipData();
|
|
||||||
const { queryByText } = render(
|
|
||||||
<ActiveMembershipsSection
|
|
||||||
activeMemberships={activeMemberships}
|
|
||||||
currentMembershipRole={currentMembershipRole}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(queryByText).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not render the membership list', () => {
|
|
||||||
const {
|
|
||||||
activeMemberships,
|
|
||||||
currentMembershipRole,
|
|
||||||
} = getEmptyMembershipData();
|
|
||||||
const { getByTestId } = render(
|
|
||||||
<ActiveMembershipsSection
|
|
||||||
activeMemberships={activeMemberships}
|
|
||||||
currentMembershipRole={currentMembershipRole}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
// no users to be found
|
|
||||||
const activeMembershipsWrapper = getByTestId('active-memberships');
|
|
||||||
|
|
||||||
expect(Number(activeMembershipsWrapper.dataset.activeCount)).toEqual(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render the membership list', () => {
|
|
||||||
const { activeMemberships, currentMembershipRole } = getMembershipData();
|
|
||||||
const { getByTestId } = render(
|
|
||||||
<ActiveMembershipsSection
|
|
||||||
activeMemberships={activeMemberships}
|
|
||||||
currentMembershipRole={currentMembershipRole}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
// the other fields aren't necessary to test as this is handled in the
|
|
||||||
// <Membership /> tests.
|
|
||||||
const activeMembershipsWrapper = getByTestId('active-memberships');
|
|
||||||
|
|
||||||
expect(
|
|
||||||
Number(activeMembershipsWrapper.dataset.activeCount),
|
|
||||||
).toBeGreaterThanOrEqual(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import { render } from '@testing-library/preact';
|
|
||||||
import { axe } from 'jest-axe';
|
|
||||||
import { Alert } from '../alert';
|
|
||||||
|
|
||||||
describe('<Alert />', () => {
|
|
||||||
it('should have no a11y violations', async () => {
|
|
||||||
const { container } = render(<Alert showAlert />);
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render an alert', () => {
|
|
||||||
const { queryByRole } = render(<Alert showAlert />);
|
|
||||||
|
|
||||||
expect(queryByRole('alert')).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not render an alert', () => {
|
|
||||||
const { queryByRole } = render(<Alert />);
|
|
||||||
|
|
||||||
const alert = queryByRole('alert');
|
|
||||||
|
|
||||||
expect(alert).toBeNull();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import { render } from '@testing-library/preact';
|
|
||||||
import fetch from 'jest-fetch-mock';
|
|
||||||
import { axe } from 'jest-axe';
|
|
||||||
import { Article } from '../article';
|
|
||||||
|
|
||||||
global.fetch = fetch;
|
|
||||||
|
|
||||||
const getArticle = () => {
|
|
||||||
return {
|
|
||||||
title: 'Your approval means nothing to me',
|
|
||||||
type_of: 'article',
|
|
||||||
path: '/princesscarolyn/your-approval-means-nothing-to-me-42640',
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('<Article />', () => {
|
|
||||||
it('should have no a11y violations', async () => {
|
|
||||||
const { container } = render(<Article resource={getArticle()} />);
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render', async () => {
|
|
||||||
const { queryByTitle } = render(<Article resource={getArticle()} />);
|
|
||||||
|
|
||||||
expect(queryByTitle('Your approval means nothing to me')).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import { render } from '@testing-library/preact';
|
|
||||||
import { axe } from 'jest-axe';
|
|
||||||
import { ChannelButton } from '../components/ChannelButton';
|
|
||||||
|
|
||||||
const getChannel = () => {
|
|
||||||
return {
|
|
||||||
channel_name: 'test',
|
|
||||||
channel_color: '#00FFFF',
|
|
||||||
channel_type: 'invite_only',
|
|
||||||
channel_modified_slug: '@test34',
|
|
||||||
id: 34,
|
|
||||||
chat_channel_id: 23,
|
|
||||||
status: 'active',
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('<ChannelButton />', () => {
|
|
||||||
it('should have no a11y violations', async () => {
|
|
||||||
const { container } = render(<ChannelButton channel={getChannel()} />);
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render', () => {
|
|
||||||
const { getByText } = render(<ChannelButton channel={getChannel()} />);
|
|
||||||
const button = getByText('test');
|
|
||||||
|
|
||||||
expect(button.dataset.channelSlug).toEqual('@test34');
|
|
||||||
expect(button.dataset.channelId).toEqual('23');
|
|
||||||
expect(button.dataset.channelStatus).toEqual('active');
|
|
||||||
expect(button.dataset.channelName).toEqual('test');
|
|
||||||
expect(button.dataset.content).toEqual('sidecar-channel-request');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import { render } from '@testing-library/preact';
|
|
||||||
import { axe } from 'jest-axe';
|
|
||||||
import { ChannelRequest } from '../channelRequest';
|
|
||||||
|
|
||||||
const getResource = () => {
|
|
||||||
return {
|
|
||||||
user: {
|
|
||||||
name: 'Sarthak',
|
|
||||||
username: 'sarthak9',
|
|
||||||
},
|
|
||||||
channel: {
|
|
||||||
name: 'IronMan',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('<ChannelRequest />', () => {
|
|
||||||
it('should have no a11y violations', async () => {
|
|
||||||
const { container } = render(<ChannelRequest resource={getResource()} />);
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render', () => {
|
|
||||||
const { queryByText, queryByAltText } = render(
|
|
||||||
<ChannelRequest resource={getResource()} />,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(queryByText('Hey Sarthak !')).toBeDefined();
|
|
||||||
expect(
|
|
||||||
queryByText(
|
|
||||||
'You are not a member of this group yet. Send a request to join.',
|
|
||||||
),
|
|
||||||
).toBeDefined();
|
|
||||||
expect(queryByAltText('sarthak9 profile')).toBeDefined();
|
|
||||||
expect(queryByAltText('IronMan profile')).toBeDefined();
|
|
||||||
expect(queryByText('Join IronMan', { selector: 'button' })).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import { render } from '@testing-library/preact';
|
|
||||||
import { axe } from 'jest-axe';
|
|
||||||
import { ChannelRequestSection } from '../RequestManager/ChannelRequestSection';
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
channelRequests: [
|
|
||||||
{
|
|
||||||
name: 'Demo name',
|
|
||||||
membership_id: 11,
|
|
||||||
user_id: 10,
|
|
||||||
role: 'member',
|
|
||||||
image: '/image',
|
|
||||||
username: 'demousername',
|
|
||||||
status: 'joining_request',
|
|
||||||
chat_channel_name: 'demo channel',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('<ChannelRequestSection />', () => {
|
|
||||||
it('should have no a11y violations', async () => {
|
|
||||||
const { container } = render(
|
|
||||||
<ChannelRequestSection channelRequests={data.channelRequests} />,
|
|
||||||
);
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render the the component', () => {
|
|
||||||
const { getByTestId } = render(
|
|
||||||
<ChannelRequestSection channelRequests={data.channelRequests} />,
|
|
||||||
);
|
|
||||||
|
|
||||||
const channelRequestsWrapper = getByTestId('chat-channel-joining-request');
|
|
||||||
expect(
|
|
||||||
Number(channelRequestsWrapper.dataset.activeCount),
|
|
||||||
).toBeGreaterThanOrEqual(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,171 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import { render, fireEvent } from '@testing-library/preact';
|
|
||||||
import { JSDOM } from 'jsdom';
|
|
||||||
import { axe } from 'jest-axe';
|
|
||||||
import { Channels } from '../channels';
|
|
||||||
|
|
||||||
const doc = new JSDOM('<!doctype html><html><body></body></html>');
|
|
||||||
global.document = doc;
|
|
||||||
global.window = doc.defaultView;
|
|
||||||
global.window.currentUser = { id: 'fake_username' };
|
|
||||||
|
|
||||||
let channelSwitched = false;
|
|
||||||
|
|
||||||
const fakeSwitchChannel = () => {
|
|
||||||
channelSwitched = !channelSwitched;
|
|
||||||
};
|
|
||||||
|
|
||||||
const fakeChannels = [
|
|
||||||
{
|
|
||||||
channel_name: 'channel name 1',
|
|
||||||
last_opened_at: 'September 2, 2018',
|
|
||||||
channel_users: [],
|
|
||||||
last_message_at: 'September 21, 2018',
|
|
||||||
channel_type: 'group',
|
|
||||||
slug: '0',
|
|
||||||
channel_modified_slug: '@0',
|
|
||||||
id: 12345,
|
|
||||||
status: 'active',
|
|
||||||
messages_count: 124,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
channel_name: 'group channel 2',
|
|
||||||
status: 'active',
|
|
||||||
last_opened_at: 'September 12, 2018',
|
|
||||||
channel_users: [
|
|
||||||
{
|
|
||||||
profile_image: 'fake_profile_image',
|
|
||||||
darker_color: '#111111',
|
|
||||||
last_opened_at: 'some last open date',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
profile_image: 'fake_profile_pic',
|
|
||||||
darker_color: '#222222',
|
|
||||||
last_opened_at: 'some other last open date',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
last_message_at: 'September 14, 2018',
|
|
||||||
channel_type: 'direct',
|
|
||||||
slug: '1',
|
|
||||||
channel_modified_slug: '@1',
|
|
||||||
id: 12345,
|
|
||||||
messages_count: 83,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
channel_name: 'group channel 3',
|
|
||||||
last_opened_at: 'September 30, 2018',
|
|
||||||
channel_users: [
|
|
||||||
{
|
|
||||||
profile_image: 'fake_profile_image',
|
|
||||||
darker_color: '#111111',
|
|
||||||
last_opened_at: 'some last open date',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
profile_image: 'fake_profile_pic',
|
|
||||||
darker_color: '#222222',
|
|
||||||
last_opened_at: 'some other last open date',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
last_message_at: 'September 29, 2018',
|
|
||||||
channel_type: 'group',
|
|
||||||
status: 'active',
|
|
||||||
slug: '2',
|
|
||||||
channel_modified_slug: '@2',
|
|
||||||
id: 67890,
|
|
||||||
messages_count: 56,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const getChannels = (mod, chatChannels) => (
|
|
||||||
<Channels
|
|
||||||
incomingVideoCallChannelIds={[]} // no incoming calls
|
|
||||||
activeChannelId={12345}
|
|
||||||
chatChannels={chatChannels}
|
|
||||||
unopenedChannelIds={[]}
|
|
||||||
handleSwitchChannel={fakeSwitchChannel}
|
|
||||||
channelsLoaded
|
|
||||||
filterQuery=""
|
|
||||||
expanded={mod}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
describe('<Channels />', () => {
|
|
||||||
describe('expanded', () => {
|
|
||||||
it('should have no a11y violations', async () => {
|
|
||||||
const { container } = render(getChannels(true, fakeChannels));
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render with chat channels', () => {
|
|
||||||
const { getByText, getByRole, queryByRole } = render(
|
|
||||||
getChannels(true, fakeChannels),
|
|
||||||
);
|
|
||||||
|
|
||||||
// welcome message should not exist because there are channels
|
|
||||||
expect(queryByRole('alert')).toBeNull();
|
|
||||||
|
|
||||||
// configFooter should exist
|
|
||||||
fireEvent.click(
|
|
||||||
getByRole('button', { name: /configuration navigation menu/i }),
|
|
||||||
);
|
|
||||||
const settings = getByText('Settings');
|
|
||||||
expect(settings.getAttribute('href')).toEqual('/settings');
|
|
||||||
|
|
||||||
const reportAbuse = getByText('Report Abuse');
|
|
||||||
expect(reportAbuse.getAttribute('href')).toEqual('/report-abuse');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render without chat channels', () => {
|
|
||||||
const { getByText, getByRole } = render(getChannels(true, []));
|
|
||||||
|
|
||||||
// should show "Welcome to Connect message....."
|
|
||||||
getByRole('alert');
|
|
||||||
|
|
||||||
fireEvent.click(
|
|
||||||
getByRole('button', { name: /configuration navigation menu/i }),
|
|
||||||
);
|
|
||||||
const settings = getByText('Settings');
|
|
||||||
expect(settings.getAttribute('href')).toEqual('/settings');
|
|
||||||
|
|
||||||
const reportAbuse = getByText('Report Abuse');
|
|
||||||
expect(reportAbuse.getAttribute('href')).toEqual('/report-abuse');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('not expanded', () => {
|
|
||||||
it('should have no a11y violations', async () => {
|
|
||||||
const { container } = render(getChannels(false, fakeChannels));
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have the proper elements, attributes, and content', () => {
|
|
||||||
const { queryByTestId } = render(getChannels(false, fakeChannels));
|
|
||||||
|
|
||||||
// should have group names but no user names
|
|
||||||
// TODO: I don't understand the comment above. To revisit.
|
|
||||||
expect(queryByTestId('chat-channels-list')).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('without chat channels', () => {
|
|
||||||
it('should have no a11y violations', async () => {
|
|
||||||
const { container } = render(getChannels(false, []));
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render without chat channels', () => {
|
|
||||||
const { getByTestId } = render(getChannels(false, []));
|
|
||||||
|
|
||||||
// should have nothing but empty str
|
|
||||||
const chatChannelList = getByTestId('chat-channels-list');
|
|
||||||
|
|
||||||
expect(chatChannelList.textContent).toEqual('');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,244 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import { fireEvent, render } from '@testing-library/preact';
|
|
||||||
import fetch from 'jest-fetch-mock';
|
|
||||||
import { JSDOM } from 'jsdom';
|
|
||||||
import { axe } from 'jest-axe';
|
|
||||||
import { Chat } from '../chat';
|
|
||||||
|
|
||||||
const doc = new JSDOM('<!doctype html><html><body></body></html>');
|
|
||||||
global.document = doc;
|
|
||||||
global.window = doc.defaultView;
|
|
||||||
|
|
||||||
// mock observer and user ID
|
|
||||||
window.IntersectionObserver = jest.fn(function intersectionObserverMock() {
|
|
||||||
this.observe = jest.fn();
|
|
||||||
});
|
|
||||||
global.window.currentUser = { id: 'some_id' };
|
|
||||||
|
|
||||||
function getRootData() {
|
|
||||||
return {
|
|
||||||
chatChannels: JSON.stringify([
|
|
||||||
{
|
|
||||||
channel_name: 'channel name 1',
|
|
||||||
last_opened_at: 'September 2, 2018',
|
|
||||||
channel_users: [],
|
|
||||||
last_message_at: 'September 21, 2018',
|
|
||||||
channel_type: 'group',
|
|
||||||
slug: '0',
|
|
||||||
id: 12345,
|
|
||||||
messages_count: 124,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
channel_name: 'group channel 2',
|
|
||||||
last_opened_at: 'September 12, 2018',
|
|
||||||
channel_users: [
|
|
||||||
{
|
|
||||||
profile_image: 'fake_profile_image',
|
|
||||||
darker_color: '#111111',
|
|
||||||
last_opened_at: 'some last open date',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
profile_image: 'fake_profile_pic',
|
|
||||||
darker_color: '#222222',
|
|
||||||
last_opened_at: 'some other last open date',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
last_message_at: 'September 14, 2018',
|
|
||||||
channel_type: 'direct',
|
|
||||||
slug: '1',
|
|
||||||
id: 34561,
|
|
||||||
messages_count: 83,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
channel_name: 'group channel 3',
|
|
||||||
last_opened_at: 'September 30, 2018',
|
|
||||||
channel_users: [
|
|
||||||
{
|
|
||||||
profile_image: 'fake_profile_image',
|
|
||||||
darker_color: '#111111',
|
|
||||||
last_opened_at: 'some last open date',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
profile_image: 'fake_profile_pic',
|
|
||||||
darker_color: '#222222',
|
|
||||||
last_opened_at: 'some other last open date',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
last_message_at: 'September 29, 2018',
|
|
||||||
channel_type: 'group',
|
|
||||||
slug: '2',
|
|
||||||
id: 67890,
|
|
||||||
messages_count: 56,
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
chatOptions: JSON.stringify({
|
|
||||||
showChannelsList: true,
|
|
||||||
showTimestamp: true,
|
|
||||||
activeChannelId: 34561,
|
|
||||||
}),
|
|
||||||
githubToken: 'somegithubtoken',
|
|
||||||
pusherKey: 'somepusherkey',
|
|
||||||
tagModerator: JSON.stringify({ isTagModerator: true }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMockResponse() {
|
|
||||||
return JSON.stringify({
|
|
||||||
result: [
|
|
||||||
{
|
|
||||||
id: 117921,
|
|
||||||
status: 'active',
|
|
||||||
viewable_by: 9597,
|
|
||||||
chat_channel_id: 51923,
|
|
||||||
last_opened_at: '2020-06-07T15:51:12.033Z',
|
|
||||||
channel_text:
|
|
||||||
'Tag Moderators tag-moderators Nick Taylor (he/him) Ben Greenberg Jess Lee (she/her) Katie Nelson ☞ Desigan Chinniah ☜',
|
|
||||||
channel_last_message_at: '2020-06-07T02:13:01.230Z',
|
|
||||||
channel_status: 'active',
|
|
||||||
channel_type: 'invite_only',
|
|
||||||
channel_username: null,
|
|
||||||
channel_name: 'Tag Moderators',
|
|
||||||
channel_image:
|
|
||||||
'https://practicaldev-herokuapp-com.freetls.fastly.net/assets/organization-d83b2b577749cb1cb5c615003fc0d379f0bacc3be6cc1f541ac5655a9d770855.svg',
|
|
||||||
channel_modified_slug: 'tag-moderators',
|
|
||||||
channel_discoverable: false,
|
|
||||||
channel_messages_count: 3287,
|
|
||||||
last_indexed_at: '2020-06-07T15:51:11.100Z',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 209670,
|
|
||||||
status: 'active',
|
|
||||||
viewable_by: 9597,
|
|
||||||
chat_channel_id: 100437,
|
|
||||||
last_opened_at: '2020-06-07T16:05:41.636Z',
|
|
||||||
channel_text:
|
|
||||||
'#react mods react-mods-5en7 Nick Taylor (he/him) Michael Tharrington (he/him) Chris Achard',
|
|
||||||
channel_last_message_at: '2020-06-05T17:03:19.872Z',
|
|
||||||
channel_status: 'active',
|
|
||||||
channel_type: 'invite_only',
|
|
||||||
channel_username: null,
|
|
||||||
channel_name: '#react mods',
|
|
||||||
channel_image:
|
|
||||||
'https://practicaldev-herokuapp-com.freetls.fastly.net/assets/organization-d83b2b577749cb1cb5c615003fc0d379f0bacc3be6cc1f541ac5655a9d770855.svg',
|
|
||||||
channel_modified_slug: 'react-mods-5en7',
|
|
||||||
channel_discoverable: false,
|
|
||||||
channel_messages_count: 7,
|
|
||||||
last_indexed_at: '2020-06-07T16:05:40.723Z',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 204169,
|
|
||||||
status: 'active',
|
|
||||||
viewable_by: 9597,
|
|
||||||
chat_channel_id: 101735,
|
|
||||||
last_opened_at: '2017-01-01T05:00:00.000Z',
|
|
||||||
channel_text:
|
|
||||||
'Direct carolskelly nickytonline carolskelly/nickytonline Nick Taylor (he/him) Carol Skelly',
|
|
||||||
channel_last_message_at: '2020-03-28T00:57:52.210Z',
|
|
||||||
channel_status: 'active',
|
|
||||||
channel_type: 'direct',
|
|
||||||
channel_username: 'carolskelly',
|
|
||||||
channel_name: '@carolskelly',
|
|
||||||
channel_image:
|
|
||||||
'https://res.cloudinary.com/practicaldev/image/fetch/s--lQ_RERs9--/c_fill,f_auto,fl_progressive,h_90,q_auto,w_90/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/11343/jDcwvKh7.jpg',
|
|
||||||
channel_modified_slug: '@carolskelly',
|
|
||||||
channel_messages_count: 0,
|
|
||||||
last_indexed_at: '2020-04-04T00:57:52.331Z',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('<Chat />', () => {
|
|
||||||
const csrfToken = 'this-is-a-csrf-token';
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
global.Pusher = jest.fn(() => ({
|
|
||||||
subscribe: jest.fn(() => ({
|
|
||||||
bind: jest.fn(),
|
|
||||||
})),
|
|
||||||
}));
|
|
||||||
global.fetch = fetch;
|
|
||||||
global.getCsrfToken = async () => csrfToken;
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
delete global.Pusher;
|
|
||||||
delete global.fetch;
|
|
||||||
delete global.getCsrfToken;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have no a11y violations', async () => {
|
|
||||||
fetch.mockResponse(getMockResponse());
|
|
||||||
const { container } = render(<Chat {...getRootData()} />);
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render expanded', () => {
|
|
||||||
fetch.mockResponse(getMockResponse());
|
|
||||||
const { getByTestId, getByText, getByRole } = render(
|
|
||||||
<Chat {...getRootData()} />,
|
|
||||||
);
|
|
||||||
const chat = getByTestId('chat');
|
|
||||||
|
|
||||||
expect(chat.getAttribute('aria-expanded')).toEqual('true');
|
|
||||||
|
|
||||||
// chat filtering
|
|
||||||
getByText('all', { selector: 'button' });
|
|
||||||
getByText('direct', { selector: 'button' });
|
|
||||||
getByText('group', { selector: 'button' });
|
|
||||||
|
|
||||||
// renderActiveChatChannel
|
|
||||||
const activeChat = getByTestId('active-chat');
|
|
||||||
|
|
||||||
expect(activeChat).not.toBeNull();
|
|
||||||
|
|
||||||
getByText('Scroll to Bottom', { selector: '[type="button"]' });
|
|
||||||
|
|
||||||
// Delete modal should be visible
|
|
||||||
getByRole('dialog', {
|
|
||||||
selector: '[aria-hidden="false"]',
|
|
||||||
});
|
|
||||||
getByText('Are you sure, you want to delete this message?');
|
|
||||||
getByText('Cancel', { selector: '[type="button"]' });
|
|
||||||
getByText('Delete', { selector: '[type="button"]' });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should collapse and expand chat channels properly', async () => {
|
|
||||||
fetch.mockResponse(getMockResponse());
|
|
||||||
const { queryByText } = render(<Chat {...getRootData()} />);
|
|
||||||
|
|
||||||
// // chat channels
|
|
||||||
expect(
|
|
||||||
queryByText('all', {
|
|
||||||
selector: 'button',
|
|
||||||
}),
|
|
||||||
).toBeDefined();
|
|
||||||
expect(
|
|
||||||
queryByText('direct', {
|
|
||||||
selector: 'button',
|
|
||||||
}),
|
|
||||||
).toBeDefined();
|
|
||||||
expect(
|
|
||||||
queryByText('group', {
|
|
||||||
selector: 'button',
|
|
||||||
}),
|
|
||||||
).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should show mention pop-up on keyUp @', async () => {
|
|
||||||
const { getByLabelText, getByTestId } = render(<Chat {...getRootData()} />);
|
|
||||||
|
|
||||||
const inputField = getByLabelText('Compose a message');
|
|
||||||
|
|
||||||
expect(inputField).toBeDefined();
|
|
||||||
fireEvent.change(inputField, { target: { value: '@' } });
|
|
||||||
|
|
||||||
const mentionModal = getByTestId('mentionList');
|
|
||||||
const allButton = getByTestId('all');
|
|
||||||
|
|
||||||
expect(mentionModal).toBeDefined();
|
|
||||||
expect(allButton).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import { render } from '@testing-library/preact';
|
|
||||||
import { axe } from 'jest-axe';
|
|
||||||
import { ChannelDescriptionSection } from '../ChatChannelSettings/ChannelDescriptionSection';
|
|
||||||
|
|
||||||
describe('<ChannelDescriptionSection />', () => {
|
|
||||||
it('should have no a11y violations', async () => {
|
|
||||||
const { container } = render(
|
|
||||||
<ChannelDescriptionSection
|
|
||||||
channelName="some name"
|
|
||||||
channelDescription="some description"
|
|
||||||
currentMembershipRole="mod"
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render', () => {
|
|
||||||
const { queryByText } = render(
|
|
||||||
<ChannelDescriptionSection
|
|
||||||
channelName="some name"
|
|
||||||
channelDescription="some description"
|
|
||||||
currentMembershipRole="member"
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(queryByText('some name')).toBeDefined();
|
|
||||||
expect(queryByText('some description')).toBeDefined();
|
|
||||||
expect(queryByText('You are a channel member')).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import { render } from '@testing-library/preact';
|
|
||||||
import { axe } from 'jest-axe';
|
|
||||||
import { ChatChannelMembershipSection } from '../ChatChannelSettings/ChatChannelMembershipSection';
|
|
||||||
|
|
||||||
describe('<ChatChannelMembershipSection />', () => {
|
|
||||||
it('should have no a11y violations', async () => {
|
|
||||||
const { container } = render(
|
|
||||||
<ChatChannelMembershipSection
|
|
||||||
pendingMemberships={[]}
|
|
||||||
requestedMemberships={[]}
|
|
||||||
activeMemberships={[]}
|
|
||||||
currentMembershipRole="mod"
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render when no memberships', () => {
|
|
||||||
const { getByText, getByTestId } = render(
|
|
||||||
<ChatChannelMembershipSection
|
|
||||||
pendingMemberships={[]}
|
|
||||||
requestedMemberships={[]}
|
|
||||||
activeMemberships={[]}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
getByTestId('active-memberships');
|
|
||||||
const activeMemberships = getByTestId('active-memberships');
|
|
||||||
|
|
||||||
expect(Number(activeMemberships.dataset.activeCount)).toEqual(0);
|
|
||||||
|
|
||||||
getByText('Members');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render with memberships', () => {
|
|
||||||
const { getByText, getByTestId } = render(
|
|
||||||
<ChatChannelMembershipSection
|
|
||||||
pendingMemberships={[{ name: 'Ben', username: 'ben' }]}
|
|
||||||
requestedMemberships={[
|
|
||||||
{ name: 'Peter', username: 'peter' },
|
|
||||||
{ name: 'Jess', username: 'jess' },
|
|
||||||
{ name: 'Xenox', username: 'xenox1' },
|
|
||||||
]}
|
|
||||||
activeMemberships={[
|
|
||||||
{ name: 'Bobby', username: 'bobby' },
|
|
||||||
{ name: 'Sarah', username: 'sarah' },
|
|
||||||
]}
|
|
||||||
currentMembershipRole="mod"
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
getByTestId('active-memberships');
|
|
||||||
const activeMemberships = getByTestId('active-memberships');
|
|
||||||
|
|
||||||
expect(Number(activeMemberships.dataset.activeCount)).toEqual(2);
|
|
||||||
|
|
||||||
getByText('Members', {
|
|
||||||
selector: '[data-testid="active-memberships"] *',
|
|
||||||
});
|
|
||||||
|
|
||||||
const pendingMemberships = getByTestId('pending-memberships');
|
|
||||||
|
|
||||||
expect(Number(pendingMemberships.dataset.pendingCount)).toEqual(1);
|
|
||||||
|
|
||||||
getByText('Pending Invitations', {
|
|
||||||
selector: '[data-testid="pending-memberships"] *',
|
|
||||||
});
|
|
||||||
|
|
||||||
const requestedMemberships = getByTestId('requested-memberships');
|
|
||||||
|
|
||||||
expect(Number(requestedMemberships.dataset.requestedCount)).toEqual(3);
|
|
||||||
|
|
||||||
getByText('Joining Request', {
|
|
||||||
selector: '[data-testid="requested-memberships"] *',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,226 +0,0 @@
|
||||||
import fetch from 'jest-fetch-mock';
|
|
||||||
import {
|
|
||||||
getChannelDetails,
|
|
||||||
updatePersonalChatChannelNotificationSettings,
|
|
||||||
rejectChatChannelJoiningRequest,
|
|
||||||
acceptChatChannelJoiningRequest,
|
|
||||||
updateChatChannelDescription,
|
|
||||||
sendChatChannelInvitation,
|
|
||||||
leaveChatChannelMembership,
|
|
||||||
updateMembershipRole,
|
|
||||||
} from '../actions/chat_channel_setting_actions';
|
|
||||||
|
|
||||||
/* global globalThis */
|
|
||||||
|
|
||||||
describe('Chat channel API requests', () => {
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Update the membership role', () => {
|
|
||||||
it('should have the success response', async () => {
|
|
||||||
const response = { success: true, message: 'user membership is updated' };
|
|
||||||
fetch.mockResponse(JSON.stringify(response));
|
|
||||||
|
|
||||||
const result = await updateMembershipRole(
|
|
||||||
chanChannelMembershipId,
|
|
||||||
channelId,
|
|
||||||
'mod',
|
|
||||||
);
|
|
||||||
expect(result).toEqual(response);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the not found', async () => {
|
|
||||||
const response = { success: false, message: 'not found' };
|
|
||||||
fetch.mockResponse(JSON.stringify(response));
|
|
||||||
|
|
||||||
const result = await updateMembershipRole('', '', 'mod');
|
|
||||||
expect(result).toEqual(response);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import { render } from '@testing-library/preact';
|
|
||||||
import { axe } from 'jest-axe';
|
|
||||||
import { ChatChannelSettings } from '../ChatChannelSettings/ChatChannelSettings';
|
|
||||||
|
|
||||||
// TODO: These tests are imcomplete, but currently
|
|
||||||
// this is simply a migration to preact-testing-library.
|
|
||||||
// More tests should be added here.
|
|
||||||
describe('<ChatChannelSettings />', () => {
|
|
||||||
it('should have no a11y violations', async () => {
|
|
||||||
const { container } = render(
|
|
||||||
<ChatChannelSettings activeMembershipId={12} />,
|
|
||||||
);
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render if there are no channels', () => {
|
|
||||||
const { container } = render(
|
|
||||||
<ChatChannelSettings activeMembershipId={12} />,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(container.firstElementChild).toBeNull();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,234 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import { render, fireEvent, createEvent } from '@testing-library/preact';
|
|
||||||
import { axe } from 'jest-axe';
|
|
||||||
import { Compose } from '../compose';
|
|
||||||
|
|
||||||
let submitNoMessage = false;
|
|
||||||
let submitWithMessage = false;
|
|
||||||
let textfieldIsEmpty = true;
|
|
||||||
|
|
||||||
const handleSubmitEmpty = () => {
|
|
||||||
submitNoMessage = true;
|
|
||||||
submitWithMessage = false;
|
|
||||||
textfieldIsEmpty = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmitFake = () => {
|
|
||||||
submitNoMessage = false;
|
|
||||||
submitWithMessage = true;
|
|
||||||
textfieldIsEmpty = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleKeyDownFake = (e) => {
|
|
||||||
const enterPressed = e.keyCode === 13;
|
|
||||||
const shiftPressed = e.shiftKey;
|
|
||||||
if (!enterPressed) {
|
|
||||||
textfieldIsEmpty = false;
|
|
||||||
} else if (textfieldIsEmpty && !shiftPressed) {
|
|
||||||
handleSubmitEmpty();
|
|
||||||
} else if (textfieldIsEmpty && shiftPressed) {
|
|
||||||
textfieldIsEmpty = false;
|
|
||||||
} else {
|
|
||||||
handleSubmitFake();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getCompose = (tf, props = {}) => {
|
|
||||||
// true -> not empty, false -> empty
|
|
||||||
if (tf) {
|
|
||||||
return (
|
|
||||||
<Compose
|
|
||||||
handleSubmitOnClick={handleSubmitFake}
|
|
||||||
handleKeyDown={handleKeyDownFake}
|
|
||||||
activeChannelId={12345}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Compose
|
|
||||||
handleSubmitOnClick={handleSubmitEmpty}
|
|
||||||
handleKeyDown={handleKeyDownFake}
|
|
||||||
activeChannelId={12345}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('<Compose />', () => {
|
|
||||||
afterEach(() => {
|
|
||||||
submitNoMessage = false;
|
|
||||||
submitWithMessage = false;
|
|
||||||
textfieldIsEmpty = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have no a11y violations', async () => {
|
|
||||||
const { container } = render(getCompose(false));
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('behavior with no message', () => {
|
|
||||||
it('should click submit', () => {
|
|
||||||
const { getByText } = render(getCompose(false));
|
|
||||||
const button = getByText(/Send/i);
|
|
||||||
|
|
||||||
button.click();
|
|
||||||
expect(submitNoMessage).toEqual(true);
|
|
||||||
expect(submitWithMessage).toEqual(false);
|
|
||||||
expect(textfieldIsEmpty).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should press enter', () => {
|
|
||||||
const { getByLabelText } = render(getCompose(false));
|
|
||||||
const input = getByLabelText('Compose a message');
|
|
||||||
|
|
||||||
fireEvent.keyDown(input, { keyCode: 13 });
|
|
||||||
|
|
||||||
expect(submitNoMessage).toEqual(true);
|
|
||||||
expect(submitWithMessage).toEqual(false);
|
|
||||||
expect(textfieldIsEmpty).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should pressed enter and shift', () => {
|
|
||||||
const { getByLabelText } = render(getCompose(false));
|
|
||||||
const input = getByLabelText('Compose a message');
|
|
||||||
|
|
||||||
fireEvent.keyDown(input, { keyCode: 13 });
|
|
||||||
fireEvent.keyDown(input, { keyCode: 16 });
|
|
||||||
|
|
||||||
expect(textfieldIsEmpty).toEqual(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('behavior with message', () => {
|
|
||||||
it('should have no a11y violations', async () => {
|
|
||||||
const { container } = render(getCompose(true));
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have proper elements, attributes and values', () => {
|
|
||||||
const { getByLabelText, getByText } = render(getCompose(true));
|
|
||||||
|
|
||||||
const input = getByLabelText('Compose a message');
|
|
||||||
|
|
||||||
expect(input.textContent).toEqual('');
|
|
||||||
expect(input.getAttribute('maxLength')).toEqual('1000');
|
|
||||||
expect(input.getAttribute('placeholder')).toContain('Write message to');
|
|
||||||
|
|
||||||
// Ensure send button is pressent
|
|
||||||
getByText(/send/i);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should click submit and check for empty textarea', () => {
|
|
||||||
const { getByLabelText, getByText } = render(getCompose(true));
|
|
||||||
const input = getByLabelText('Compose a message');
|
|
||||||
const sendButton = getByText(/send/i);
|
|
||||||
|
|
||||||
fireEvent.keyDown(input, { keyCode: 69 });
|
|
||||||
expect(textfieldIsEmpty).toEqual(false);
|
|
||||||
|
|
||||||
sendButton.click();
|
|
||||||
|
|
||||||
expect(submitNoMessage).toEqual(false);
|
|
||||||
expect(submitWithMessage).toEqual(true);
|
|
||||||
expect(textfieldIsEmpty).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should press enter and check for empty textarea', () => {
|
|
||||||
const { getByLabelText } = render(getCompose(true));
|
|
||||||
const input = getByLabelText('Compose a message');
|
|
||||||
|
|
||||||
fireEvent.keyDown(input, { keyCode: 69 });
|
|
||||||
fireEvent.keyDown(input, { keyCode: 13 });
|
|
||||||
|
|
||||||
expect(submitNoMessage).toEqual(false);
|
|
||||||
expect(submitWithMessage).toEqual(true);
|
|
||||||
expect(textfieldIsEmpty).toEqual(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check for the actual input value after pressing enter
|
|
||||||
it('should press enter and check for empty input', () => {
|
|
||||||
const compose = getCompose(true);
|
|
||||||
const { getByTestId, rerender } = render(compose);
|
|
||||||
|
|
||||||
const input = getByTestId('messageform');
|
|
||||||
|
|
||||||
fireEvent(input, createEvent('input', input, { target: { value: 'T' } }));
|
|
||||||
|
|
||||||
expect(input.value).toBe('T');
|
|
||||||
|
|
||||||
fireEvent.keyDown(input, { keyCode: 13 });
|
|
||||||
|
|
||||||
rerender(compose);
|
|
||||||
|
|
||||||
expect(input.value).toBe('');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check for the actual input value after clicking send
|
|
||||||
it('should click send and check for empty input', () => {
|
|
||||||
const compose = getCompose(true);
|
|
||||||
const { getByTestId, getByText, rerender } = render(compose);
|
|
||||||
|
|
||||||
const input = getByTestId('messageform');
|
|
||||||
const sendButton = getByText(/send/i);
|
|
||||||
|
|
||||||
fireEvent(input, createEvent('input', input, { target: { value: 'T' } }));
|
|
||||||
|
|
||||||
expect(input.value).toBe('T');
|
|
||||||
|
|
||||||
sendButton.click();
|
|
||||||
|
|
||||||
rerender(compose);
|
|
||||||
|
|
||||||
expect(input.value).toBe('');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check for the actual input value after saving an edit
|
|
||||||
it('should click send edit and check for empty input', () => {
|
|
||||||
const compose = getCompose(true, {
|
|
||||||
markdownEdited: false,
|
|
||||||
startEditing: true,
|
|
||||||
editMessageMarkdown: 'Test',
|
|
||||||
handleSubmitOnClickEdit: () => null,
|
|
||||||
});
|
|
||||||
const { getByTestId, getByText, rerender } = render(compose);
|
|
||||||
|
|
||||||
const input = getByTestId('messageform');
|
|
||||||
const saveButton = getByText(/save/i);
|
|
||||||
|
|
||||||
expect(input.value).toBe('Test');
|
|
||||||
|
|
||||||
saveButton.click();
|
|
||||||
|
|
||||||
rerender(compose);
|
|
||||||
|
|
||||||
expect(input.value).toBe('');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check for the actual input value after canceling an edit
|
|
||||||
it('should click close edit and check for empty input', () => {
|
|
||||||
const compose = getCompose(true, {
|
|
||||||
markdownEdited: false,
|
|
||||||
startEditing: true,
|
|
||||||
editMessageMarkdown: 'Test',
|
|
||||||
handleEditMessageClose: () => null,
|
|
||||||
});
|
|
||||||
const { getByTestId, getByText, rerender } = render(compose);
|
|
||||||
|
|
||||||
const input = getByTestId('messageform');
|
|
||||||
const closeButton = getByText(/close/i);
|
|
||||||
|
|
||||||
expect(input.value).toBe('Test');
|
|
||||||
|
|
||||||
closeButton.click();
|
|
||||||
|
|
||||||
rerender(compose);
|
|
||||||
|
|
||||||
expect(input.value).toBe('');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import { render } from '@testing-library/preact';
|
|
||||||
import { axe } from 'jest-axe';
|
|
||||||
import { Content } from '../content';
|
|
||||||
|
|
||||||
const getChannelRequestData = () => ({
|
|
||||||
onTriggerContent: jest.fn(),
|
|
||||||
type_of: 'channel-request',
|
|
||||||
activeChannelId: 12345,
|
|
||||||
pusherKey: 'ASDFGHJKL',
|
|
||||||
githubToken: '',
|
|
||||||
data: {
|
|
||||||
channel: {
|
|
||||||
name: 'bobby',
|
|
||||||
},
|
|
||||||
user: {
|
|
||||||
username: 'spongebob',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const getLoadingUserData = () => ({
|
|
||||||
onTriggerContent: jest.fn(),
|
|
||||||
type_of: 'loading-user',
|
|
||||||
activeChannelId: 1235,
|
|
||||||
pusherKey: 'ASDFGHJKL',
|
|
||||||
githubToken: '',
|
|
||||||
data: {
|
|
||||||
user: {
|
|
||||||
username: 'spongebob',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('<Content />', () => {
|
|
||||||
describe('as loading-user', () => {
|
|
||||||
it('should have no a11y violations', async () => {
|
|
||||||
const channelRequestResource = getChannelRequestData();
|
|
||||||
const { container } = render(
|
|
||||||
<Content resource={channelRequestResource} />,
|
|
||||||
);
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render', () => {
|
|
||||||
const channelRequestResource = getChannelRequestData();
|
|
||||||
const { queryByText, queryByTitle } = render(
|
|
||||||
<Content resource={channelRequestResource} />,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Ensure the two buttons render
|
|
||||||
expect(queryByTitle('exit')).toBeDefined();
|
|
||||||
expect(queryByTitle('fullscreen')).toBeDefined();
|
|
||||||
|
|
||||||
// Simple check if the component to request joining a channel appears.
|
|
||||||
// The component itself is tested it in it's own test suite.
|
|
||||||
expect(
|
|
||||||
queryByText(
|
|
||||||
'You are not a member of this group yet. Send a request to join.',
|
|
||||||
),
|
|
||||||
).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('as channel-request', () => {
|
|
||||||
it('should have no a11y violations', async () => {
|
|
||||||
const loadinUserResource = getLoadingUserData();
|
|
||||||
const { container } = render(<Content resource={loadinUserResource} />);
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render', () => {
|
|
||||||
const loadinUserResource = getLoadingUserData();
|
|
||||||
const { queryByTitle } = render(
|
|
||||||
<Content resource={loadinUserResource} />,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Ensure the two buttons render
|
|
||||||
expect(queryByTitle('exit')).toBeDefined();
|
|
||||||
expect(queryByTitle('fullscreen')).toBeDefined();
|
|
||||||
expect(queryByTitle('Loading user')).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
/*
|
|
||||||
testing only as loading user since components that Content uses
|
|
||||||
are independently tested
|
|
||||||
*/
|
|
||||||
});
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import { render } from '@testing-library/preact';
|
|
||||||
import { axe } from 'jest-axe';
|
|
||||||
import { Draw } from '../draw';
|
|
||||||
|
|
||||||
describe('<Draw />', () => {
|
|
||||||
it('should have no a11y violations', async () => {
|
|
||||||
const { container } = render(<Draw />);
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render', () => {
|
|
||||||
const { queryByText } = render(<Draw />);
|
|
||||||
|
|
||||||
expect(queryByText('Connect Draw')).not.toBeNull();
|
|
||||||
expect(queryByText('Clear')).not.toBeNull();
|
|
||||||
expect(queryByText('Send')).not.toBeNull();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import { render } from '@testing-library/preact';
|
|
||||||
import { axe } from 'jest-axe';
|
|
||||||
import { InviationLinkManager } from '../ChatChannelSettings/MembershipManager/InvitationLinkManager';
|
|
||||||
|
|
||||||
const currentModMembership = {
|
|
||||||
name: 'dummy user',
|
|
||||||
username: 'dummyuser',
|
|
||||||
user_id: 1,
|
|
||||||
chat_channel_id: 2,
|
|
||||||
status: 'active',
|
|
||||||
role: 'mod',
|
|
||||||
};
|
|
||||||
|
|
||||||
const currentMemberMembership = {
|
|
||||||
name: 'dummy member',
|
|
||||||
username: 'dummymember',
|
|
||||||
user_id: 1,
|
|
||||||
chat_channel_id: 2,
|
|
||||||
status: 'active',
|
|
||||||
role: 'member',
|
|
||||||
};
|
|
||||||
|
|
||||||
const svg = (
|
|
||||||
<svg
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
className="crayons-icon copy-icon"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
role="img"
|
|
||||||
aria-labelledby="fc5f15add1e114844f5e"
|
|
||||||
>
|
|
||||||
<title id="fc5f15add1e114844f5e">Copy Invitation Url</title>
|
|
||||||
<path d="M7 6V3a1 1 0 011-1h12a1 1 0 011 1v14a1 1 0 01-1 1h-3v3c0 .552-.45 1-1.007 1H4.007A1 1 0 013 21l.003-14c0-.552.45-1 1.007-1H7zm2 0h8v10h2V4H9v2zm-2 5v2h6v-2H7zm0 4v2h6v-2H7z" />
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
|
|
||||||
describe('<InviationLinkManager />', () => {
|
|
||||||
it('should have no a11y violations', async () => {
|
|
||||||
const { container } = render(
|
|
||||||
<InviationLinkManager
|
|
||||||
invitationLink="https://dummy-invitation.link"
|
|
||||||
currentMembership={currentModMembership}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render', () => {
|
|
||||||
const { queryByText } = render(
|
|
||||||
<InviationLinkManager
|
|
||||||
invitationLink="https://dummy-invitation.link"
|
|
||||||
currentMembership={currentModMembership}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(queryByText('https://dummy-invitation.link')).toBeDefined();
|
|
||||||
expect(queryByText('Invitation Link')).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not render', () => {
|
|
||||||
const { rerender } = render(
|
|
||||||
<InviationLinkManager
|
|
||||||
invitationLink="https://dummy-invitation.link"
|
|
||||||
currentMembership={currentMemberMembership}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(rerender()).toEqual(undefined);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import { render } from '@testing-library/preact';
|
|
||||||
import { axe } from 'jest-axe';
|
|
||||||
import { InviteForm } from '../ChatChannelSettings/InviteForm';
|
|
||||||
|
|
||||||
describe('<InviteForm />', () => {
|
|
||||||
it('should have no a11y violations', async () => {
|
|
||||||
const { container } = render(<InviteForm invitationUsernames="" />);
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render with no usernames', () => {
|
|
||||||
const { queryByLabelText, queryByText } = render(
|
|
||||||
<InviteForm invitationUsernames="" />,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(queryByLabelText('Usernames to invite')).toBeDefined();
|
|
||||||
expect(queryByText('Submit')).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render with usernames to invite', () => {
|
|
||||||
const { getByLabelText } = render(
|
|
||||||
<InviteForm invitationUsernames="@bobbytables, @xss, @owasp" />,
|
|
||||||
);
|
|
||||||
|
|
||||||
const input = getByLabelText('Usernames to invite');
|
|
||||||
|
|
||||||
expect(input.value).toEqual('@bobbytables, @xss, @owasp');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import { render } from '@testing-library/preact';
|
|
||||||
import { axe } from 'jest-axe';
|
|
||||||
import { LeaveMembershipSection } from '../ChatChannelSettings/LeaveMembershipSection';
|
|
||||||
|
|
||||||
describe('<LeaveMembershipSection />', () => {
|
|
||||||
it('should have no a11y violations', async () => {
|
|
||||||
const { container } = render(<LeaveMembershipSection />);
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render', () => {
|
|
||||||
const { queryByText } = render(<LeaveMembershipSection />);
|
|
||||||
|
|
||||||
expect(queryByText('Danger Zone')).toBeDefined();
|
|
||||||
expect(queryByText('Leave Channel')).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have user leave channel when leave button is clicked', () => {
|
|
||||||
const leaveHandler = jest.fn();
|
|
||||||
const { getByText } = render(
|
|
||||||
<LeaveMembershipSection handleleaveChannelMembership={leaveHandler} />,
|
|
||||||
);
|
|
||||||
const leaveButton = getByText('Leave Channel');
|
|
||||||
|
|
||||||
leaveButton.click();
|
|
||||||
|
|
||||||
expect(leaveHandler).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import { render } from '@testing-library/preact';
|
|
||||||
import '@testing-library/jest-dom';
|
|
||||||
import { axe } from 'jest-axe';
|
|
||||||
import { Message } from '../message';
|
|
||||||
|
|
||||||
const msg = {
|
|
||||||
username: 'asdf',
|
|
||||||
used_id: 12345,
|
|
||||||
message: 'WE BUILT THIS CITY',
|
|
||||||
color: '#00FFFF',
|
|
||||||
};
|
|
||||||
|
|
||||||
const getMessage = (message, props) => (
|
|
||||||
<Message
|
|
||||||
user={message.username}
|
|
||||||
userID={message.user_id}
|
|
||||||
message={message.message}
|
|
||||||
color={message.color}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
describe('<Message />', () => {
|
|
||||||
it('should have no a11y violations', async () => {
|
|
||||||
const { container } = render(getMessage(msg));
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render', () => {
|
|
||||||
const { getByText, getByAltText } = render(getMessage(msg));
|
|
||||||
|
|
||||||
getByAltText('asdf profile');
|
|
||||||
getByText(msg.message);
|
|
||||||
|
|
||||||
const profileLink = getByText(msg.username);
|
|
||||||
|
|
||||||
expect(profileLink.parentElement).toHaveStyle({ color: msg.color });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should highlight @mentions to the logged in user', () => {
|
|
||||||
const testMessage = {
|
|
||||||
username: 'testUser',
|
|
||||||
user_id: 456,
|
|
||||||
message: "<p>hello <a href='testUser'>@testUser</a></p>",
|
|
||||||
};
|
|
||||||
|
|
||||||
const { getByText } = render(
|
|
||||||
getMessage(testMessage, { currentUserId: testMessage.user_id }),
|
|
||||||
);
|
|
||||||
|
|
||||||
const profileLink = getByText(`@${testMessage.username}`);
|
|
||||||
|
|
||||||
expect(profileLink.parentElement.innerHTML).toMatch(
|
|
||||||
new RegExp(`<mark>@${testMessage.username}</mark>`, 'i'),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import { render } from '@testing-library/preact';
|
|
||||||
import { axe } from 'jest-axe';
|
|
||||||
import { ModFaqSection } from '../ChatChannelSettings/ModFaqSection';
|
|
||||||
|
|
||||||
describe('<ChannelDescriptionSection />', () => {
|
|
||||||
it('should have no a11y violations', async () => {
|
|
||||||
const { container } = render(<ModFaqSection />);
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render', () => {
|
|
||||||
const { queryByText } = render(<ModFaqSection />);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
queryByText(/^Questions about Connect Channel moderation\? Contact/),
|
|
||||||
).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import { render } from '@testing-library/preact';
|
|
||||||
import { axe } from 'jest-axe';
|
|
||||||
import { ModSection } from '../ChatChannelSettings/ModSection';
|
|
||||||
|
|
||||||
describe('<ModSection />', () => {
|
|
||||||
it('should have no a11y violations', async () => {
|
|
||||||
const { container } = render(<ModSection currentMembershipRole="mod" />);
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render if the membership role is a moderator', () => {
|
|
||||||
const { queryByTestId } = render(
|
|
||||||
<ModSection currentMembershipRole="mod" />,
|
|
||||||
);
|
|
||||||
|
|
||||||
// the <InviteForm /> and <SettingsForm /> have their own tests.
|
|
||||||
expect(queryByTestId('invite-form')).toBeDefined();
|
|
||||||
expect(queryByTestId('settings-form')).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import { render } from '@testing-library/preact';
|
|
||||||
import { axe } from 'jest-axe';
|
|
||||||
import { PendingMembershipSection } from '../ChatChannelSettings/PendingMembershipSection';
|
|
||||||
|
|
||||||
function getEmptyMembershipData() {
|
|
||||||
return {
|
|
||||||
activeMemberships: [],
|
|
||||||
currentMembershipRole: 'mod',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMembershipData() {
|
|
||||||
return {
|
|
||||||
pendingMemberships: [
|
|
||||||
{
|
|
||||||
name: 'test user',
|
|
||||||
username: 'testusername',
|
|
||||||
user_id: '1',
|
|
||||||
membership_id: '2',
|
|
||||||
role: 'mod',
|
|
||||||
status: 'active',
|
|
||||||
image: '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
membershipType: 'active',
|
|
||||||
currentMembershipRole: 'mod',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('<PendingMembershipSection />', () => {
|
|
||||||
it('should have no a11y violations when there are no members', async () => {
|
|
||||||
const {
|
|
||||||
pendingMemberships,
|
|
||||||
currentMembershipRole,
|
|
||||||
} = getEmptyMembershipData();
|
|
||||||
const { container } = render(
|
|
||||||
<PendingMembershipSection
|
|
||||||
pendingMemberships={pendingMemberships}
|
|
||||||
currentMembershipRole={currentMembershipRole}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have no a11y violations when there are members', async () => {
|
|
||||||
const { pendingMemberships, currentMembershipRole } = getMembershipData();
|
|
||||||
const { container } = render(
|
|
||||||
<PendingMembershipSection
|
|
||||||
pendingMemberships={pendingMemberships}
|
|
||||||
currentMembershipRole={currentMembershipRole}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not render the membership list', () => {
|
|
||||||
const {
|
|
||||||
pendingMemberships,
|
|
||||||
currentMembershipRole,
|
|
||||||
} = getEmptyMembershipData();
|
|
||||||
const { getByTestId } = render(
|
|
||||||
<PendingMembershipSection
|
|
||||||
pendingMemberships={pendingMemberships}
|
|
||||||
currentMembershipRole={currentMembershipRole}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
// no users to be found
|
|
||||||
const pendingMembershipsWrapper = getByTestId('pending-memberships');
|
|
||||||
|
|
||||||
expect(Number(pendingMembershipsWrapper.dataset.pendingCount)).toEqual(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render the membership list', () => {
|
|
||||||
const { pendingMemberships, currentMembershipRole } = getMembershipData();
|
|
||||||
const { getByTestId } = render(
|
|
||||||
<PendingMembershipSection
|
|
||||||
pendingMemberships={pendingMemberships}
|
|
||||||
currentMembershipRole={currentMembershipRole}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
// no users to be found
|
|
||||||
const pendingMembershipsWrapper = getByTestId('pending-memberships');
|
|
||||||
|
|
||||||
expect(
|
|
||||||
Number(pendingMembershipsWrapper.dataset.pendingCount),
|
|
||||||
).toBeGreaterThanOrEqual(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import { render } from '@testing-library/preact';
|
|
||||||
import { axe } from 'jest-axe';
|
|
||||||
import { PersonalSettng } from '../ChatChannelSettings/PersonalSetting';
|
|
||||||
|
|
||||||
describe('<PersonalSettng />', () => {
|
|
||||||
it('should have no a11y violations', async () => {
|
|
||||||
const { container } = render(
|
|
||||||
<PersonalSettng showGlobalBadgeNotification />,
|
|
||||||
);
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render the the component', () => {
|
|
||||||
const { queryByText, queryByLabelText } = render(
|
|
||||||
<PersonalSettng showGlobalBadgeNotification />,
|
|
||||||
);
|
|
||||||
|
|
||||||
// get the section header
|
|
||||||
expect(queryByText('Personal Settings')).toBeDefined();
|
|
||||||
|
|
||||||
// get the subsection header
|
|
||||||
expect(queryByText('Notifications')).toBeDefined();
|
|
||||||
|
|
||||||
// form fields
|
|
||||||
expect(
|
|
||||||
queryByLabelText('Receive Notifications for New Messages'),
|
|
||||||
).toBeDefined();
|
|
||||||
expect(queryByText('Submit', { selector: 'button' })).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import { render } from '@testing-library/preact';
|
|
||||||
import { axe } from 'jest-axe';
|
|
||||||
import { ReportAbuse } from '../ReportAbuse';
|
|
||||||
|
|
||||||
describe('<ReportAbuse />', () => {
|
|
||||||
it('should have no a11y violations', async () => {
|
|
||||||
const { container } = render(
|
|
||||||
<ReportAbuse
|
|
||||||
data={{
|
|
||||||
message: 'HI',
|
|
||||||
user_id: 1,
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render the component', () => {
|
|
||||||
const { getByText, getByLabelText } = render(
|
|
||||||
<ReportAbuse data={{ message: 'HI', user_id: 1 }} />,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(getByText('Report Abuse')).toBeDefined();
|
|
||||||
|
|
||||||
const vulgarInput = getByLabelText('Rude or vulgar');
|
|
||||||
expect(vulgarInput.value).toEqual('rude or vulgar');
|
|
||||||
|
|
||||||
const harassmentInput = getByLabelText('Harassment or hate speech');
|
|
||||||
expect(harassmentInput.value).toEqual('harassment');
|
|
||||||
|
|
||||||
const listingsInput = getByLabelText(
|
|
||||||
'Inappropriate listings message/category',
|
|
||||||
);
|
|
||||||
expect(listingsInput.value).toEqual('listings');
|
|
||||||
|
|
||||||
const spamInput = getByLabelText('Spam or copyright issue');
|
|
||||||
expect(spamInput.value).toEqual('spam');
|
|
||||||
|
|
||||||
expect(getByText('Report Message')).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import { render } from '@testing-library/preact';
|
|
||||||
import fetch from 'jest-fetch-mock';
|
|
||||||
import { axe } from 'jest-axe';
|
|
||||||
import { beforeEach } from '@jest/globals';
|
|
||||||
import { RequestManager } from '../RequestManager/RequestManager';
|
|
||||||
import '@testing-library/jest-dom';
|
|
||||||
|
|
||||||
function getData() {
|
|
||||||
const data = [
|
|
||||||
{
|
|
||||||
resource: {},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: There needs to be some better tests in here in regards to different data: empty, some data etc.
|
|
||||||
describe('<RequestManager />', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
const csrfToken = 'this-is-a-csrf-token';
|
|
||||||
|
|
||||||
window.fetch = fetch;
|
|
||||||
window.getCsrfToken = async () => csrfToken;
|
|
||||||
|
|
||||||
fetch.mockResponse(
|
|
||||||
JSON.stringify({
|
|
||||||
result: { user_joining_requests: [], channel_joining_memberships: [] },
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have no a11y violations', async () => {
|
|
||||||
const { container } = render(
|
|
||||||
<RequestManager resource={getData()} updateRequestCount={jest.fn()} />,
|
|
||||||
);
|
|
||||||
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have the proper elements', () => {
|
|
||||||
const { getByText } = render(
|
|
||||||
<RequestManager resource={getData()} updateRequestCount={jest.fn()} />,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(getByText('You have no pending invitations.')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import { render } from '@testing-library/preact';
|
|
||||||
import { axe } from 'jest-axe';
|
|
||||||
import { RequestedMembershipSection } from '../ChatChannelSettings/RequestedMembershipSection';
|
|
||||||
|
|
||||||
function getEmptyMembershipRequestsData() {
|
|
||||||
return {
|
|
||||||
requestedMemberships: [],
|
|
||||||
currentMembershipRole: 'mod',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMembershipData() {
|
|
||||||
return {
|
|
||||||
requestedMemberships: [
|
|
||||||
{
|
|
||||||
name: 'test user',
|
|
||||||
username: 'testusername',
|
|
||||||
user_id: '1',
|
|
||||||
membership_id: '2',
|
|
||||||
role: 'member',
|
|
||||||
status: 'requested',
|
|
||||||
image: '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
membershipType: 'requested',
|
|
||||||
currentMembershipRole: 'mod',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('<RequestedMembershipSection />', () => {
|
|
||||||
it('should have no a11y violations when there are no requested memberships', async () => {
|
|
||||||
const {
|
|
||||||
requestedMemberships,
|
|
||||||
currentMembershipRole,
|
|
||||||
} = getEmptyMembershipRequestsData();
|
|
||||||
const { container } = render(
|
|
||||||
<RequestedMembershipSection
|
|
||||||
requestedMemberships={requestedMemberships}
|
|
||||||
currentMembershipRole={currentMembershipRole}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have no a11y violations when there are requested memberships', async () => {
|
|
||||||
const { requestedMemberships, currentMembershipRole } = getMembershipData();
|
|
||||||
const { container } = render(
|
|
||||||
<RequestedMembershipSection
|
|
||||||
requestedMemberships={requestedMemberships}
|
|
||||||
currentMembershipRole={currentMembershipRole}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not render the membership list', () => {
|
|
||||||
const {
|
|
||||||
requestedMemberships,
|
|
||||||
currentMembershipRole,
|
|
||||||
} = getEmptyMembershipRequestsData();
|
|
||||||
const { getByText, queryByText } = render(
|
|
||||||
<RequestedMembershipSection
|
|
||||||
requestedMemberships={requestedMemberships}
|
|
||||||
currentMembershipRole={currentMembershipRole}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
getByText('Joining Request');
|
|
||||||
|
|
||||||
expect(
|
|
||||||
queryByText('+', { selector: 'button[data-membership-id]' }),
|
|
||||||
).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render the membership list', () => {
|
|
||||||
const { requestedMemberships, currentMembershipRole } = getMembershipData();
|
|
||||||
const { queryByText } = render(
|
|
||||||
<RequestedMembershipSection
|
|
||||||
requestedMemberships={requestedMemberships}
|
|
||||||
currentMembershipRole={currentMembershipRole}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(queryByText('Joining Request')).toBeDefined();
|
|
||||||
expect(
|
|
||||||
queryByText('+', { selector: 'button[data-membership-id]' }),
|
|
||||||
).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import { render } from '@testing-library/preact';
|
|
||||||
import { axe } from 'jest-axe';
|
|
||||||
import { SettingsForm } from '../ChatChannelSettings/SettingsForm';
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
channelDescription: 'some description test',
|
|
||||||
channelDiscoverable: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
const getSettingsForm = (channelDetails) => {
|
|
||||||
return (
|
|
||||||
<SettingsForm
|
|
||||||
showGlobalBadgeNotification={channelDetails.showGlobalBadgeNotification}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('<SettingsForm />', () => {
|
|
||||||
it('should have no a11y violations', async () => {
|
|
||||||
const { container } = render(getSettingsForm(data));
|
|
||||||
const results = await axe(container);
|
|
||||||
|
|
||||||
expect(results).toHaveNoViolations();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render the the component', () => {
|
|
||||||
const { queryByText, queryByLabelText } = render(getSettingsForm(data));
|
|
||||||
|
|
||||||
// title
|
|
||||||
expect(queryByText('Channel Settings')).toBeDefined();
|
|
||||||
|
|
||||||
// description of channel
|
|
||||||
expect(queryByLabelText('Description')).toBeDefined();
|
|
||||||
|
|
||||||
// whether or not the channel is discoverable
|
|
||||||
expect(queryByLabelText('Channel Discoverable')).toBeDefined();
|
|
||||||
|
|
||||||
// submit buttton
|
|
||||||
expect(queryByText('Submit')).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
import { getUserDataAndCsrfToken } from '../util';
|
|
||||||
|
|
||||||
const ERROR_MESSAGE = "Couldn't find user data on page.";
|
|
||||||
|
|
||||||
describe('Chat utilities', () => {
|
|
||||||
describe('getUserDataAndCsrfToken', () => {
|
|
||||||
afterEach(() => {
|
|
||||||
document.head.innerHTML = '';
|
|
||||||
document.body.removeAttribute('data-user');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should reject if no user or csrf token found.', async () => {
|
|
||||||
await expect(getUserDataAndCsrfToken(document)).rejects.toThrow(
|
|
||||||
ERROR_MESSAGE,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should reject if csrf token found but no user.', async () => {
|
|
||||||
document.head.innerHTML =
|
|
||||||
'<meta name="csrf-token" content="some-csrf-token" />';
|
|
||||||
await expect(getUserDataAndCsrfToken(document)).rejects.toThrow(
|
|
||||||
ERROR_MESSAGE,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should reject if user found but no csrf token found.', async () => {
|
|
||||||
document.body.setAttribute('data-user', '{}');
|
|
||||||
await expect(getUserDataAndCsrfToken(document)).rejects.toThrow(
|
|
||||||
ERROR_MESSAGE,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should resolve if user and csrf token found.', async () => {
|
|
||||||
const csrfToken = 'some-csrf-token';
|
|
||||||
const currentUser = {
|
|
||||||
id: 41,
|
|
||||||
name: 'Guy Fieri',
|
|
||||||
username: 'guyfieri',
|
|
||||||
profile_image_90:
|
|
||||||
'/uploads/user/profile_image/41/0841dbe2-208c-4daa-b498-b2f01f3d37b2.png',
|
|
||||||
followed_tag_names: [],
|
|
||||||
followed_tags: '[]',
|
|
||||||
reading_list_ids: [48, 49, 34, 51, 64, 56],
|
|
||||||
saw_onboarding: true,
|
|
||||||
checked_code_of_conduct: false,
|
|
||||||
display_sponsors: true,
|
|
||||||
trusted: false,
|
|
||||||
};
|
|
||||||
document.head.innerHTML = `<meta name="csrf-token" content="${csrfToken}" />`;
|
|
||||||
document.body.setAttribute('data-user', JSON.stringify(currentUser));
|
|
||||||
|
|
||||||
expect(await getUserDataAndCsrfToken(document)).toEqual({
|
|
||||||
currentUser,
|
|
||||||
csrfToken,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { adjustTimestamp } from './util';
|
|
||||||
|
|
||||||
export const ActionMessage = ({
|
|
||||||
user,
|
|
||||||
message,
|
|
||||||
color,
|
|
||||||
timestamp,
|
|
||||||
profileImageUrl,
|
|
||||||
onContentTrigger,
|
|
||||||
}) => {
|
|
||||||
const spanStyle = { color };
|
|
||||||
|
|
||||||
const messageArea = (
|
|
||||||
<span
|
|
||||||
className="chatmessagebody__message"
|
|
||||||
// eslint-disable-next-line react/no-danger
|
|
||||||
dangerouslySetInnerHTML={{ __html: message }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="chatmessage chatmessage__action ">
|
|
||||||
<div className="chatmessage__profilepic">
|
|
||||||
<a
|
|
||||||
href={`/${user}`}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
data-content="sidecar-user"
|
|
||||||
onClick={onContentTrigger}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
role="presentation"
|
|
||||||
className="chatmessagebody__profileimage"
|
|
||||||
src={profileImageUrl}
|
|
||||||
alt={`${user} profile`}
|
|
||||||
data-content="sidecar-user"
|
|
||||||
onClick={onContentTrigger}
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
role="presentation"
|
|
||||||
className="chatmessage__body"
|
|
||||||
onClick={onContentTrigger}
|
|
||||||
>
|
|
||||||
<div className="message__info__actions">
|
|
||||||
<div className="message__info">
|
|
||||||
<span
|
|
||||||
className="chatmessagebody__username not-dark-theme-text-compatible"
|
|
||||||
style={spanStyle}
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
className="chatmessagebody__username--link"
|
|
||||||
href={`/${user}`}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
data-content="sidecar-user"
|
|
||||||
onClick={onContentTrigger}
|
|
||||||
>
|
|
||||||
{user}
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
<span className="chatmessage__timestamp">
|
|
||||||
{`${adjustTimestamp(timestamp)}`}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="chatmessage__bodytext">{messageArea}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
ActionMessage.propTypes = {
|
|
||||||
user: PropTypes.string.isRequired,
|
|
||||||
color: PropTypes.string.isRequired,
|
|
||||||
message: PropTypes.string.isRequired,
|
|
||||||
timestamp: PropTypes.string,
|
|
||||||
profileImageUrl: PropTypes.string,
|
|
||||||
onContentTrigger: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
ActionMessage.defaultProps = {
|
|
||||||
profileImageUrl: '',
|
|
||||||
timestamp: null,
|
|
||||||
};
|
|
||||||
|
|
@ -1,182 +0,0 @@
|
||||||
import { createDataHash } from '../util';
|
|
||||||
|
|
||||||
export function getAllMessages(channelId, messageOffset, successCb, failureCb) {
|
|
||||||
fetch(`/chat_channels/${channelId}?message_offset=${messageOffset}`, {
|
|
||||||
Accept: 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
credentials: 'same-origin',
|
|
||||||
})
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then(successCb)
|
|
||||||
.catch(failureCb);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sendMessage(messageObject, successCb, failureCb) {
|
|
||||||
fetch('/messages', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
'X-CSRF-Token': window.csrfToken,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
message: {
|
|
||||||
message_markdown: messageObject.message,
|
|
||||||
user_id: window.currentUser.id,
|
|
||||||
chat_channel_id: messageObject.activeChannelId,
|
|
||||||
mentioned_users_id: messageObject.mentionedUsersId,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
credentials: 'same-origin',
|
|
||||||
})
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then(successCb)
|
|
||||||
.catch(failureCb);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function editMessage(editedMessage, successCb, failureCb) {
|
|
||||||
fetch(`/messages/${editedMessage.id}`, {
|
|
||||||
method: 'PATCH',
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
'X-CSRF-Token': window.csrfToken,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
message: {
|
|
||||||
message_markdown: editedMessage.message,
|
|
||||||
user_id: window.currentUser.id,
|
|
||||||
chat_channel_id: editedMessage.activeChannelId,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
credentials: 'same-origin',
|
|
||||||
})
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then(successCb)
|
|
||||||
.catch(failureCb);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sendOpen(activeChannelId, successCb, failureCb) {
|
|
||||||
fetch(`/chat_channels/${activeChannelId}/open`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
'X-CSRF-Token': window.csrfToken,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({}),
|
|
||||||
credentials: 'same-origin',
|
|
||||||
})
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then(successCb)
|
|
||||||
.catch(failureCb);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function conductModeration(
|
|
||||||
activeChannelId,
|
|
||||||
message,
|
|
||||||
successCb,
|
|
||||||
failureCb,
|
|
||||||
) {
|
|
||||||
fetch(`/chat_channels/${activeChannelId}/moderate`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
'X-CSRF-Token': window.csrfToken,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
chat_channel: {
|
|
||||||
command: message,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
credentials: 'same-origin',
|
|
||||||
})
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then(successCb)
|
|
||||||
.catch(failureCb);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getChannels(
|
|
||||||
searchParams,
|
|
||||||
additionalFilters,
|
|
||||||
successCb,
|
|
||||||
_failureCb,
|
|
||||||
) {
|
|
||||||
return createDataHash(additionalFilters, searchParams).then((response) => {
|
|
||||||
if (
|
|
||||||
searchParams.retrievalID === null ||
|
|
||||||
response.result.filter(
|
|
||||||
(e) => e.chat_channel_id === searchParams.retrievalID,
|
|
||||||
).length === 1
|
|
||||||
) {
|
|
||||||
successCb(response.result, searchParams.query);
|
|
||||||
} else {
|
|
||||||
fetch(
|
|
||||||
`/chat_channel_memberships/find_by_chat_channel_id?chat_channel_id=${searchParams.retrievalID}`,
|
|
||||||
{
|
|
||||||
Accept: 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
credentials: 'same-origin',
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.then((individualResponse) => individualResponse.json())
|
|
||||||
.then((json) => {
|
|
||||||
response.result.unshift(json);
|
|
||||||
successCb(response.result, searchParams.query);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getUnopenedChannelIds(successCb) {
|
|
||||||
fetch('/chat_channels?state=unopened_ids', {
|
|
||||||
credentials: 'same-origin',
|
|
||||||
})
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then((json) => {
|
|
||||||
successCb(json.unopened_ids);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getContent(url, successCb, failureCb) {
|
|
||||||
fetch(url, {
|
|
||||||
Accept: 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
credentials: 'same-origin',
|
|
||||||
})
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then(successCb)
|
|
||||||
.catch(failureCb);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getJSONContents(url, successCb, failureCb) {
|
|
||||||
fetch(url, {
|
|
||||||
Accept: 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
credentials: 'same-origin',
|
|
||||||
})
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then(successCb)
|
|
||||||
.catch(failureCb);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteMessage(messageId, successCb, failureCb) {
|
|
||||||
fetch(`/messages/${messageId}`, {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
'X-CSRF-Token': window.csrfToken,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
message: {
|
|
||||||
user_id: window.currentUser.id,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
credentials: 'same-origin',
|
|
||||||
})
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then(successCb)
|
|
||||||
.catch(failureCb);
|
|
||||||
}
|
|
||||||
|
|
@ -1,186 +0,0 @@
|
||||||
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}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
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',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
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',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function is used to update the membership role
|
|
||||||
* @param {number} membershipId selected User Chat channel membership id
|
|
||||||
* @param {number} chatChannelId Current chat chaneel id
|
|
||||||
* @param {string} role updated role for the membership
|
|
||||||
*/
|
|
||||||
export async function updateMembershipRole(membershipId, chatChannelId, role) {
|
|
||||||
const response = await request(
|
|
||||||
`/chat_channel_memberships/update_membership_role/${chatChannelId}`,
|
|
||||||
{
|
|
||||||
method: 'PATCH',
|
|
||||||
body: {
|
|
||||||
chat_channel_membership: {
|
|
||||||
chat_channel_id: chatChannelId,
|
|
||||||
membership_id: membershipId,
|
|
||||||
role,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create Chat Channel
|
|
||||||
* @param {string} channelName
|
|
||||||
* @param {string} userNames
|
|
||||||
*/
|
|
||||||
|
|
||||||
export async function createChannel(channelName, userNames) {
|
|
||||||
const response = await request(`/create_channel`, {
|
|
||||||
method: 'POST',
|
|
||||||
body: {
|
|
||||||
chat_channel: {
|
|
||||||
channel_name: channelName,
|
|
||||||
invitation_usernames: userNames,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
|
|
@ -1,143 +0,0 @@
|
||||||
import { request } from '../../utilities/http';
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {channelId} channelId
|
|
||||||
* @param {membershipId} membershipId
|
|
||||||
*/
|
|
||||||
export async function rejectJoiningRequest(channelId, membershipId) {
|
|
||||||
const response = await request(
|
|
||||||
`/chat_channel_memberships/remove_membership`,
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
body: {
|
|
||||||
status: 'pending',
|
|
||||||
chat_channel_id: channelId,
|
|
||||||
membership_id: membershipId,
|
|
||||||
},
|
|
||||||
credentials: 'same-origin',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function is responsible for the Accept joining request for channel
|
|
||||||
* @param {number} channelId
|
|
||||||
* @param {number} membershipId
|
|
||||||
*/
|
|
||||||
|
|
||||||
export async function acceptJoiningRequest(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 function sendChannelRequest(id, successCb, failureCb) {
|
|
||||||
request(`/join_chat_channel`, {
|
|
||||||
method: 'POST',
|
|
||||||
body: {
|
|
||||||
chat_channel_membership: {
|
|
||||||
chat_channel_id: id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
credentials: 'same-origin',
|
|
||||||
})
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then(successCb)
|
|
||||||
.catch(failureCb);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function will get all the request realted to user and channel
|
|
||||||
*/
|
|
||||||
|
|
||||||
export async function getChannelRequestInfo() {
|
|
||||||
const response = await request(`/channel_request_info/`, {
|
|
||||||
method: 'GET',
|
|
||||||
credentials: 'same-origin',
|
|
||||||
});
|
|
||||||
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function handle user action on chat channel invitations
|
|
||||||
*
|
|
||||||
* @param {number} membershipId
|
|
||||||
* @param {string} userAction
|
|
||||||
*/
|
|
||||||
|
|
||||||
export async function updateMembership(membershipId, userAction) {
|
|
||||||
const response = await request(`/chat_channel_memberships/${membershipId}`, {
|
|
||||||
method: 'PUT',
|
|
||||||
credentials: 'same-origin',
|
|
||||||
body: {
|
|
||||||
chat_channel_membership: {
|
|
||||||
user_action: userAction,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string} feedback_message
|
|
||||||
* @param {string} type_of_feedback
|
|
||||||
* @param {string} category
|
|
||||||
* @param {string} reported_url
|
|
||||||
*/
|
|
||||||
export async function reportAbuse(
|
|
||||||
feedback_message,
|
|
||||||
feedback_type,
|
|
||||||
category,
|
|
||||||
offender_id,
|
|
||||||
) {
|
|
||||||
const response = await request('/feedback_messages', {
|
|
||||||
method: 'POST',
|
|
||||||
body: {
|
|
||||||
feedback_message: {
|
|
||||||
message: feedback_message,
|
|
||||||
feedback_type,
|
|
||||||
category,
|
|
||||||
offender_id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Blocks a user with the given ID from using Connect
|
|
||||||
*
|
|
||||||
* @param {number} userId
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
export async function blockUser(userId) {
|
|
||||||
const response = await request('/user_blocks', {
|
|
||||||
method: 'POST',
|
|
||||||
body: {
|
|
||||||
user_block: {
|
|
||||||
blocked_id: userId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
export const Alert = ({ showAlert }) => {
|
|
||||||
const otherClassname = showAlert ? '' : 'chatalert__default--hidden';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
role="alert"
|
|
||||||
aria-hidden={!showAlert}
|
|
||||||
className={`chatalert__default ${otherClassname}`}
|
|
||||||
>
|
|
||||||
More new messages below
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
Alert.propTypes = {
|
|
||||||
showAlert: PropTypes.bool.isRequired,
|
|
||||||
};
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
export const Article = ({ resource: article }) => (
|
|
||||||
<div className="activechatchannel__activeArticle">
|
|
||||||
<iframe
|
|
||||||
id="activecontent-iframe"
|
|
||||||
src={article.path}
|
|
||||||
title={article.title}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
Article.propTypes = {
|
|
||||||
resource: PropTypes.shape({
|
|
||||||
id: PropTypes.string,
|
|
||||||
}).isRequired,
|
|
||||||
};
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { Button } from '@crayons';
|
|
||||||
|
|
||||||
export const ChannelRequest = ({ resource: data, handleJoiningRequest }) => (
|
|
||||||
<div>
|
|
||||||
<div className="joining-message">
|
|
||||||
<h2>Hey {data.user.name} !</h2>
|
|
||||||
<h3>You are not a member of this group yet. Send a request to join.</h3>
|
|
||||||
</div>
|
|
||||||
<div className="user-picture">
|
|
||||||
<div className="chatmessage__profilepic">
|
|
||||||
<img
|
|
||||||
className="chatmessagebody__profileimage"
|
|
||||||
src={data.user.profile_image_90}
|
|
||||||
alt={`${data.user.username} profile`}
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
className="chatmessagebody__profileimage"
|
|
||||||
src="/assets/organization.svg"
|
|
||||||
alt={`${data.channel.name} profile`}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="send-request">
|
|
||||||
{data.channel.status !== 'joining_request' ? (
|
|
||||||
<Button
|
|
||||||
variant="primary"
|
|
||||||
onClick={handleJoiningRequest}
|
|
||||||
data-channel-id={data.channel.id}
|
|
||||||
>
|
|
||||||
{' '}
|
|
||||||
Join {data.channel.name}{' '}
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<Button variant="secondary"> Requested Already </Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
ChannelRequest.propTypes = {
|
|
||||||
resource: PropTypes.shape({
|
|
||||||
data: PropTypes.object,
|
|
||||||
}).isRequired,
|
|
||||||
handleJoiningRequest: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
@ -1,119 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { ChannelButton } from './components/ChannelButton';
|
|
||||||
import { ConfigMenu } from './configMenu';
|
|
||||||
import { channelSorter } from './util';
|
|
||||||
|
|
||||||
export const Channels = ({
|
|
||||||
activeChannelId,
|
|
||||||
chatChannels,
|
|
||||||
unopenedChannelIds,
|
|
||||||
handleSwitchChannel,
|
|
||||||
expanded,
|
|
||||||
filterQuery = '',
|
|
||||||
channelsLoaded,
|
|
||||||
currentUserId,
|
|
||||||
triggerActiveContent,
|
|
||||||
}) => {
|
|
||||||
const sortedChatChannels = channelSorter(
|
|
||||||
chatChannels,
|
|
||||||
currentUserId,
|
|
||||||
filterQuery,
|
|
||||||
);
|
|
||||||
const discoverableChannels = sortedChatChannels.discoverableChannels.map(
|
|
||||||
(channel) => {
|
|
||||||
return (
|
|
||||||
<ChannelButton
|
|
||||||
channel={channel}
|
|
||||||
key={channel.chat_channel_id}
|
|
||||||
discoverableChannel
|
|
||||||
triggerActiveContent={triggerActiveContent}
|
|
||||||
isActiveChannel={
|
|
||||||
parseInt(activeChannelId, 10) === channel.chat_channel_id
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const channels = sortedChatChannels.activeChannels.map((channel) => {
|
|
||||||
const isActive = parseInt(activeChannelId, 10) === channel.chat_channel_id;
|
|
||||||
const isUnopened =
|
|
||||||
!isActive && unopenedChannelIds.includes(channel.chat_channel_id);
|
|
||||||
const newMessagesIndicator = isUnopened ? 'new' : 'old';
|
|
||||||
const otherClassname = isActive
|
|
||||||
? 'chatchanneltab--active'
|
|
||||||
: 'chatchanneltab--inactive';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ChannelButton
|
|
||||||
channel={channel}
|
|
||||||
key={channel.chat_channel_id}
|
|
||||||
newMessagesIndicator={newMessagesIndicator}
|
|
||||||
otherClassname={otherClassname}
|
|
||||||
handleSwitchChannel={handleSwitchChannel}
|
|
||||||
isUnopened={isUnopened}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
let topNotice = '';
|
|
||||||
if (
|
|
||||||
expanded &&
|
|
||||||
filterQuery.length === 0 &&
|
|
||||||
channelsLoaded &&
|
|
||||||
(channels.length === 0 || channels[0].messages_count === 0)
|
|
||||||
) {
|
|
||||||
topNotice = (
|
|
||||||
<div className="chatchannels__channelslistheader" role="alert">
|
|
||||||
<span role="img" aria-label="emoji">
|
|
||||||
👋
|
|
||||||
</span>{' '}
|
|
||||||
Welcome to
|
|
||||||
<b> Connect</b>! You may message anyone you mutually follow.
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let channelsListFooter = '';
|
|
||||||
if (channels.length === 30) {
|
|
||||||
channelsListFooter = (
|
|
||||||
<div className="chatchannels__channelslistfooter">...</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className="chatchannels">
|
|
||||||
<div
|
|
||||||
className="chatchannels__channelslist"
|
|
||||||
id="chatchannels__channelslist"
|
|
||||||
data-testid="chat-channels-list"
|
|
||||||
>
|
|
||||||
{topNotice}
|
|
||||||
{channels}
|
|
||||||
{discoverableChannels.length > 0 && filterQuery.length > 0 ? (
|
|
||||||
<div>
|
|
||||||
<span className="crayons-indicator crayons-indicator--">
|
|
||||||
Global Channel Search
|
|
||||||
</span>
|
|
||||||
{discoverableChannels}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
''
|
|
||||||
)}
|
|
||||||
{channelsListFooter}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ConfigMenu />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
Channels.propTypes = {
|
|
||||||
activeChannelId: PropTypes.number.isRequired,
|
|
||||||
chatChannels: PropTypes.arrayOf(PropTypes.objectOf()).isRequired,
|
|
||||||
unopenedChannelIds: PropTypes.arrayOf().isRequired,
|
|
||||||
handleSwitchChannel: PropTypes.func.isRequired,
|
|
||||||
triggerActiveContent: PropTypes.func.isRequired,
|
|
||||||
expanded: PropTypes.bool.isRequired,
|
|
||||||
filterQuery: PropTypes.string.isRequired,
|
|
||||||
channelsLoaded: PropTypes.bool.isRequired,
|
|
||||||
currentUserId: PropTypes.string.isRequired,
|
|
||||||
};
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,93 +0,0 @@
|
||||||
import { h, createRef } from 'preact';
|
|
||||||
import { useEffect } from 'preact/hooks';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { defaultChannelPropTypes } from '../../common-prop-types/channel-list-prop-type';
|
|
||||||
import { ChannelImage } from './ChannelImage';
|
|
||||||
import { Button } from '@crayons';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render a button to switch focus to a channel
|
|
||||||
*
|
|
||||||
* @param {object} props
|
|
||||||
*
|
|
||||||
* @component
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
*
|
|
||||||
* <ChannelButton
|
|
||||||
* channel={channel}
|
|
||||||
newMessagesIndicator={newMessagesIndicator}
|
|
||||||
otherClassname={otherClassname}
|
|
||||||
handleSwitchChannel={handleSwitchChannel}
|
|
||||||
isUnopened={isUnopened}
|
|
||||||
* />
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function ChannelButton(props) {
|
|
||||||
const buttonRef = createRef();
|
|
||||||
const { isActiveChannel = false } = props;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isActiveChannel) {
|
|
||||||
buttonRef.current.click();
|
|
||||||
}
|
|
||||||
}, [isActiveChannel, buttonRef]);
|
|
||||||
|
|
||||||
const {
|
|
||||||
channel,
|
|
||||||
handleSwitchChannel,
|
|
||||||
otherClassname,
|
|
||||||
newMessagesIndicator,
|
|
||||||
isUnopened,
|
|
||||||
discoverableChannel,
|
|
||||||
triggerActiveContent,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
ref={buttonRef}
|
|
||||||
key={channel.id}
|
|
||||||
className={
|
|
||||||
discoverableChannel
|
|
||||||
? 'chatchanneltab chatchanneltab--inactive crayons-btn--ghost align-left'
|
|
||||||
: `chatchanneltab ${otherClassname} chatchanneltab--${newMessagesIndicator} crayons-btn--ghost align-left`
|
|
||||||
}
|
|
||||||
onClick={discoverableChannel ? triggerActiveContent : handleSwitchChannel}
|
|
||||||
data-content="sidecar-channel-request"
|
|
||||||
data-channel-id={channel.chat_channel_id}
|
|
||||||
data-channel-slug={channel.channel_modified_slug}
|
|
||||||
data-channel-status={channel.status}
|
|
||||||
data-channel-name={channel.channel_name}
|
|
||||||
>
|
|
||||||
<ChannelImage
|
|
||||||
channel={channel}
|
|
||||||
newMessagesIndicator={newMessagesIndicator}
|
|
||||||
discoverableChannel={discoverableChannel}
|
|
||||||
/>
|
|
||||||
{isUnopened ? (
|
|
||||||
<span className="crayons-indicator crayons-indicator--accent crayons-indicator--bullet" />
|
|
||||||
) : null}
|
|
||||||
{channel.channel_name}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ChannelButton.propTypes = {
|
|
||||||
channel: defaultChannelPropTypes,
|
|
||||||
discoverableChannel: PropTypes.bool,
|
|
||||||
handleSwitchChannel: PropTypes.func,
|
|
||||||
triggerActiveContent: PropTypes.func,
|
|
||||||
newMessagesIndicator: PropTypes.string,
|
|
||||||
otherClassname: PropTypes.string,
|
|
||||||
isUnopened: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
ChannelButton.defaultProps = {
|
|
||||||
otherClassname: '',
|
|
||||||
isUnopened: '',
|
|
||||||
newMessagesIndicator: '',
|
|
||||||
discoverableChannel: false,
|
|
||||||
handleSwitchChannel: null,
|
|
||||||
triggerActiveContent: null,
|
|
||||||
};
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { Button } from '@crayons';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This component renders a button that is used for filtering chat channels.
|
|
||||||
*
|
|
||||||
* @param {object} props
|
|
||||||
* @param {string} props.type - The type of chat channel selected
|
|
||||||
* @param {string} props.name - Used for testing and is displayed to the user on the button
|
|
||||||
* @param {boolean} props.active - Should the button have the `active` CSS class`?
|
|
||||||
* @param {function} props.onClick - Fired with the onClick trigger
|
|
||||||
*
|
|
||||||
* @component
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* <ChannelFilterButton
|
|
||||||
* type="all"
|
|
||||||
* name="all"
|
|
||||||
* active={state.channelTypeFilter === 'all'}
|
|
||||||
* onClick={this.triggerChannelTypeFilter}
|
|
||||||
* />
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function ChannelFilterButton({ type, name, active, onClick }) {
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
data-channel-type={type}
|
|
||||||
data-testid={name}
|
|
||||||
onClick={onClick}
|
|
||||||
className={`chat__channeltypefilterbutton crayons-indicator crayons-indicator--${
|
|
||||||
active ? 'accent' : ''
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{name}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ChannelFilterButton.propTypes = {
|
|
||||||
type: PropTypes.string.isRequired,
|
|
||||||
name: PropTypes.string.isRequired,
|
|
||||||
active: PropTypes.bool.isRequired,
|
|
||||||
onClick: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
import { h } from 'preact';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { defaultChannelPropTypes } from '../../common-prop-types/channel-list-prop-type';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an image to help users identify chat channels
|
|
||||||
*
|
|
||||||
* @param {object} channel - Contains information about the channel this image represents
|
|
||||||
* @param {string} newMessagesIndicator - Used to construct a CSS classname
|
|
||||||
* @param {boolean} discoverableChannel - Used to determine which CSS class should be applied
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* <ChannelImage
|
|
||||||
channel={{channel_name: "Example Channel",
|
|
||||||
channel_type: "direct",
|
|
||||||
channel_modified_slug: "someslug-f7ff2c5a6a",
|
|
||||||
id: 1,
|
|
||||||
chat_channel_id: 20,
|
|
||||||
status: "active",
|
|
||||||
channel_image: "/some/path/to/image"}}
|
|
||||||
newMessagesIndicator="old"
|
|
||||||
discoverableChannel={false}
|
|
||||||
/>
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function ChannelImage({
|
|
||||||
channel,
|
|
||||||
newMessagesIndicator,
|
|
||||||
discoverableChannel,
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
data-channel-slug={channel.channel_modified_slug}
|
|
||||||
className={
|
|
||||||
discoverableChannel
|
|
||||||
? 'chatchanneltabindicator'
|
|
||||||
: `chatchanneltabindicator chatchanneltabindicator--${newMessagesIndicator}`
|
|
||||||
}
|
|
||||||
data-channel-id={channel.chat_channel_id}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src={channel.channel_image}
|
|
||||||
alt="pic"
|
|
||||||
data-channel-id={channel.chat_channel_id}
|
|
||||||
className={
|
|
||||||
channel.channel_type === 'direct'
|
|
||||||
? 'chatchanneltabindicatordirectimage'
|
|
||||||
: 'chatchanneltabindicatordirectimage invert-channel-image'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ChannelImage.propTypes = {
|
|
||||||
channel: defaultChannelPropTypes,
|
|
||||||
newMessagesIndicator: PropTypes.string,
|
|
||||||
discoverableChannel: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
import { h, createContext } from 'preact';
|
|
||||||
import { useReducer } from 'preact/hooks';
|
|
||||||
|
|
||||||
const store = createContext();
|
|
||||||
const { Provider } = store;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides state management for the Connect feature of Forem.
|
|
||||||
*
|
|
||||||
* @param {object} props
|
|
||||||
* @param {JSX.Element} props.children Child React elements
|
|
||||||
* @param {object} props.initialState The initial value to store as state
|
|
||||||
* @param {Function} props.reducer Given the current state and an action, returns the new state
|
|
||||||
*/
|
|
||||||
function ConnectStateProvider({ children, initialState, reducer }) {
|
|
||||||
const [state, dispatch] = useReducer(reducer, initialState);
|
|
||||||
|
|
||||||
return <Provider value={{ state, dispatch }}>{children}</Provider>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export { store, ConnectStateProvider };
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue