Implement open inbox for dev-connect (#1563)

This commit is contained in:
Mario See 2019-03-15 14:39:57 -04:00 committed by Mac Siri
parent 18a111418b
commit d8709c0334
18 changed files with 347 additions and 23 deletions

View file

@ -12,6 +12,7 @@ function callInitalizers(){
if (document.getElementsByTagName('body')[0].getAttribute('data-loaded') == "true") {
clearInterval(waitingForDataLoad);
if (document.getElementsByTagName('body')[0].getAttribute('data-user-status') == "logged-in") {
initializeAllChatButtons();
initializeBaseUserData();
}
initializeReadingListPage();

View file

@ -0,0 +1,106 @@
function initModal() {
var modal = document.querySelector('.modal');
modal.querySelector('.close-modal').addEventListener('click', toggleModal);
modal.querySelector('.overlay').addEventListener('click', toggleModal);
}
function toggleModal() {
var modal = document.querySelector('.modal');
var currentState = modal.style.display;
if (currentState === 'none') {
showChatModal(modal);
} else {
hideChatModal(modal);
}
}
function showChatModal(modal) {
modal.style.display = 'block';
document.getElementById('new-message').focus();
}
function hideChatModal(modal) {
modal.style.display = 'none';
}
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);
button.style.display = 'initial'; // show button
linkWrap.style.display = 'initial'; // show button
form.onsubmit = function() {handleChatButtonPress(form); return false;};
} else if (response === 'mutual') {
button.removeEventListener('click', toggleModal);
button.style.display = 'initial'; // show button
linkWrap.style.display = 'initial'; // 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 = function() {
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);
}
}

View file

@ -73,9 +73,12 @@ function handleTagButtAssignment(user, butt, buttInfo) {
function assignInitialButtResponse(response, butt) {
butt.classList.add('showing');
if (response === 'true') {
if (response === 'true' || response === 'mutual') {
assignState(butt, 'unfollow');
}
else if (response === 'follow-back') {
assignState(butt, 'follow-back')
}
else if (response === 'false') {
assignState(butt, 'follow');
}
@ -114,7 +117,7 @@ function handleOptimisticButtRender(butt) {
}catch (err) {return}
});
}catch (err) {return}
handleFollowButtPress(butt);
}
}
@ -129,15 +132,16 @@ function handleFollowButtPress(butt) {
}
function assignState(butt, newState) {
var style = '';
if (butt.dataset.info) {
style = JSON.parse(butt.dataset.info).style;
}
var style = JSON.parse(butt.dataset.info).style;
butt.classList.add('showing');
if (newState === 'follow') {
if (newState === 'follow' || newState === 'follow-back') {
butt.dataset.verb = 'unfollow';
butt.classList.remove('following-butt');
addFollowText(butt, style);
if (newState === 'follow-back') {
addFollowText(butt, newState);
} else if (newState === 'follow') {
addFollowText(butt, style);
}
} else if (newState === 'login') {
addFollowText(butt, style);
} else if (newState === 'self') {
@ -167,4 +171,3 @@ function addFollowingText(butt, style) {
butt.innerHTML = '✓ FOLLOWING';
}
}

View file

@ -49,6 +49,18 @@ function sendFetch(switchStatement, body) {
credentials: 'same-origin',
});
};
case 'chat-creation':
return function(csrfToken) {
body.append('authenticity_token', csrfToken);
return window.fetch('/open_chat', {
method: 'POST',
headers: {
'X-CSRF-Token': csrfToken,
},
body: body,
credentials: 'same-origin',
});
};
case 'comment-creation':
return function(csrfToken) {
return window.fetch('/comments', {

View file

@ -279,13 +279,13 @@
text-align:left;
}
}
.user-profile-follow-button-wrapper{
.user-profile-follow-button-wrapper {
position:relative;
z-index:5;
display:inline-block;
margin-left: 5px;
}
.user-profile-follow-button{
.user-profile-follow-button {
background: $green;
color:white;
border:0;
@ -314,4 +314,135 @@
min-width: 140px;
}
}
// same as above, just add class to above style
.user-profile-chat-button-wrapper {
position:relative;
z-index:5;
display:inline-block;
margin-left: 5px;
}
.user-profile-chat-button {
background: $green;
color:white;
border:0;
font-size:17px;
border-radius:3px;
vertical-align:3px;
padding:2px 6px;
cursor:pointer;
min-width: 60px;
font-family: "HelveticaNeue-CondensedBold", "HelveticaNeueBoldCondensed", "HelveticaNeue-Bold-Condensed", "Helvetica Neue Bold Condensed", "HelveticaNeueBold", "HelveticaNeue-Bold", "Helvetica Neue Bold", "HelveticaNeue", "Helvetica Neue", "TeXGyreHerosCnBold", "Helvetica", "Tahoma", "Geneva", "Arial Narrow", "Arial", sans-serif;
-webkit-appearance: none;
font-stretch: condensed;
&.showing{
opacity:1;
}
@media screen and ( min-width: 430px ){
font-size:18px;
padding:3px 8px;
vertical-align:7px;
border-radius:5px;
min-width: 80px;
}
@media screen and ( min-width: 950px ){
font-size:22px;
padding:4px 8px;
vertical-align:12px;
border-radius:5px;
min-width: 80px;
}
}
// new message modal for open inbox
.modal {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 990;
}
.modal .overlay {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 995;
background: rgba(0,0,0,0.85);
}
.modal-content {
z-index: 999;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
max-height: 80%;
overflow: auto;
background: #fff;
box-sizing: border-box;
padding: 20px;
border: 1px solid #d6d6d6;
box-shadow: 3px 3px 0px #bababa;
border-radius: 3px;
max-width: 90%;
width: 80%;
.message-form {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-around;
align-items: center;
align-content: stretch;
textarea {
border-radius: 3px;
border: 1px solid #d6d6d6;
font-size: 14px;
margin: 10px;
padding: 5px;
width: calc(100% - 100px);
order: 1;
flex-grow: 1;
resize: vertical;
max-height:30%;
}
.submit-message {
margin: 10px 10px 10px 0;
border-radius: 3px;
background: $dark-purple;
font-weight: 600px;
font-family: $helvetica-condensed;
width: 80px;
color: white;
font-size: 18px;
height: 80px;
order: 10;
flex-grow: 1;
}
}
.close-modal {
position: absolute;
right: 10px;
top: 10px;
cursor: pointer;
font-size: 18px;
opacity: 0.5;
background: none;
border: none;
padding: 0 0 0 10px;
transition: opacity 0.2s ease;
&:hover {
opacity: 0.9;
}
}
}
}

View file

@ -93,6 +93,25 @@ class ChatChannelsController < ApplicationController
end
end
def open_chat
chat_recipient = User.find(params[:user_id])
if chat_recipient.inbox_type == "open"
chat = ChatChannel.create_with_users([current_user, chat_recipient], "direct")
# get message param to generate message to send
# message_markdown = "Hi #{chat_recipient.username}! I am #{current_user.username}. I can message you on DEV Connect because your inbox is open."
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: 200
else
render json: { status: "error", message: "not allowed!" }, status: 400
end
end
private
def chat_channel_params

View file

@ -10,8 +10,13 @@ class FollowsController < ApplicationController
if current_user.id == params[:id].to_i && params[:followable_type] == "User"
render plain: "self"
return
elsif params[:followable_type] == "User" && FollowChecker.new(current_user, params[:followable_type], params[:id]).cached_follow_check && FollowChecker.new(User.find(params[:id]), params[:followable_type], current_user.id).cached_follow_check
render plain: "mutual"
elsif params[:followable_type] == "User" && FollowChecker.new(User.find(params[:id]), params[:followable_type], current_user.id).cached_follow_check
render plain: "follow-back"
else
render plain: FollowChecker.new(current_user, params[:followable_type], params[:id]).cached_follow_check
end
render plain: FollowChecker.new(current_user, params[:followable_type], params[:id]).cached_follow_check
end
def create

View file

@ -131,6 +131,7 @@ class User < ApplicationRecord
length: { maximum: 500 }
validates :mentee_description, :mentor_description,
length: { maximum: 1000 }
validates :inbox_type, inclusion: { in: ["open", "private"] }
validate :conditionally_validate_summary
validate :validate_mastodon_url
validate :validate_feed_url

View file

@ -98,6 +98,7 @@ class UserPolicy < ApplicationPolicy
mostly_work_with
name
offering_mentorship
inbox_type
permit_adjacent_sponsors
password
password_confirmation

View file

@ -40,11 +40,12 @@
<% else %>
<% if @user.email_public &&
@user.employer_name.present? &&
@user.location.present? &&
@user.education.present? &&
@user.education.size > 25 &&
@user.summary.to_s.size < 145 %>
@user.employer_name.present? &&
@user.location.present? &&
@user.education.present? &&
@user.education.size > 25 &&
@user.summary.to_s.size < 145
%>
<style>
.user-profile-header {
min-height: 398px;

View file

@ -144,3 +144,19 @@
</div>
<% end %>
<% end %>
<% if false %>
<h2> Connect </h2>
<h3> Inbox Type </h3>
<%= form_for(@user) do |f| %>
<div class="field checkbox-label-first">
<%= f.label :inbox_type, "Open your inbox to messages from people you don't follow or keep your inbox private to mutual follows."%>
<div class="sub-field">
<%= f.select :inbox_type, [['Open','open'],['Private','private']]%>
</div>
<%= f.submit "SUBMIT", class: "cta" %>
</div>
<% end %>
<% end %>

View file

@ -29,6 +29,12 @@
<span class="user-profile-follow-button-wrapper">
<button id="user-follow-butt" class="cta follow-action-button user-profile-follow-button" style="color:<%= user_colors(@user)[:text] %>;background-color:<%= user_colors(@user)[:bg] %>" data-info='{"id":<%= @user.id %>,"className":"<%= @user.class.name %>"}'>&nbsp;</button>
</span>
<span class="user-profile-chat-button-wrapper">
<a href="/connect/@<%= @user.username %>" id="user-connect-redirect" style="display:none">
<button class="chat-action-button user-profile-chat-button" id="modal-opener" style="color:<%= user_colors(@user)[:text] %>;background-color:<%=user_colors(@user)[:bg] %>; display:none; ">CHAT</button>
</a>
</span>
</h1>
<p class="profile-description" itemprop="description">
<%= @user.summary.presence || ["404 bio not found"].sample %>
@ -97,6 +103,16 @@
</p>
</div>
</div>
<div class="modal" style="display: none">
<div class="overlay"></div>
<div class="modal-content " id="new-message-modal">
<button title="Close" class="close-modal">X</button>
<form id="new-message-form" class="message-form" data-info='{"id":<%= @user.id%>,"className":"<%= @user.class.name %>","username":"<%= @user.username %>", "showChat":"<%= @user.inbox_type %>"}'>
<textarea id="new-message" rows="4" cols="70" placeholder="Enter your message here..."></textarea>
<button type="submit" value="Submit" class="submit-message">SEND</button>
</form>
</div>
</div>
<%= render "articles/user_metadata", context: "profile" %>
</div>
<% end %>

View file

@ -141,6 +141,7 @@ Rails.application.routes.draw do
post "/chat_channels/:id/open" => "chat_channels#open"
get "/connect" => "chat_channels#index"
get "/connect/:slug" => "chat_channels#index"
post "/open_chat" => "chat_channels#open_chat"
post "/pusher/auth" => "pusher#auth"

View file

@ -0,0 +1,5 @@
class AddInboxToUser < ActiveRecord::Migration[5.1]
def change
add_column :users, :inbox, :string, default: "private"
end
end

View file

@ -0,0 +1,5 @@
class ChangeInboxName < ActiveRecord::Migration[5.1]
def change
rename_column :users, :inbox, :inbox_type
end
end

View file

@ -2,12 +2,12 @@
### Issue Levels
* Level 5: Mission critical. Tends to impact all users. (i.e. production stuck in an infinite heroku deployment)
- Level 5: Mission critical. Tends to impact all users. (i.e. production stuck in an infinite heroku deployment)
* Level 4: Not mission critical but needs to be taken care of as quickly as possible. Tends to impact a subset of users. (i.e. form submission broken on mentorship settings)
- Level 4: Not mission critical but needs to be taken care of as quickly as possible. Tends to impact a subset of users. (i.e. form submission broken on mentorship settings)
* Level 3: Issue that can wait until the next sprint planning, but definitely needs to be discussed. Often an edge case that sparks discussion -- is this a patch or do we need to re-architect something? (i.e. running out of memory where a few pages get 500 errors)
- Level 3: Issue that can wait until the next sprint planning, but definitely needs to be discussed. Often an edge case that sparks discussion -- is this a patch or do we need to re-architect something? (i.e. running out of memory where a few pages get 500 errors)
* Level 2: Issue is not affecting users. Likely an internal tool that needs to be fixed. (i.e. slack notifications not pinging)
- Level 2: Issue is not affecting users. Likely an internal tool that needs to be fixed. (i.e. slack notifications not pinging)
* Level 1: Tech that stopped working but also isn't related to users/the site. (i.e. the gumball machine we use to generate random winners is broken)
- Level 1: Tech that stopped working but also isn't related to users/the site. (i.e. the gumball machine we use to generate random winners is broken)

View file

@ -36,6 +36,7 @@ RSpec.describe User, type: :model do
it { is_expected.to validate_presence_of(:username) }
it { is_expected.to validate_length_of(:username).is_at_most(30).is_at_least(2) }
it { is_expected.to validate_length_of(:name).is_at_most(100) }
it { is_expected.to validate_inclusion_of(:inbox_type).in_array(["open", "private"]) }
end
# the followings are failing

View file

@ -21,7 +21,7 @@ RSpec.describe "Follows #show", type: :request do
expect(response.body).to eq("not-logged-in")
end
it "returns false when not followeing" do
it "returns false when not following" do
expect(get_following_status.uniq[0]).to eq("false")
end