MVP of follow sidebar
This commit is contained in:
parent
64d21a3266
commit
be14ed029d
13 changed files with 224 additions and 4 deletions
|
|
@ -24,6 +24,7 @@
|
|||
@import 'live';
|
||||
@import 'delete-confirm';
|
||||
@import 'preact/onboarding-modal';
|
||||
@import 'preact/sidebar-widget';
|
||||
@import 'tag-edit';
|
||||
|
||||
@import 'ltags/LiquidTags';
|
||||
|
|
|
|||
52
app/assets/stylesheets/preact/sidebar-widget.scss
Normal file
52
app/assets/stylesheets/preact/sidebar-widget.scss
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
@import 'variables';
|
||||
|
||||
.sidebar-additional{
|
||||
.widget{
|
||||
.widget-suggested-follows-container{
|
||||
.widget-body{
|
||||
.widget-list-item__suggestions{
|
||||
.widget-list-item__close-button{
|
||||
float: right;
|
||||
width: 24px;
|
||||
height: 19px;
|
||||
margin: 3px;
|
||||
}
|
||||
.widget-list-item__content{
|
||||
max-width: 180px;
|
||||
margin-left: 60px;
|
||||
margin-right: 20px;
|
||||
min-height: 55px;
|
||||
a{
|
||||
display: block;
|
||||
padding: 5px 0px;
|
||||
font-size: 18px;
|
||||
color: $black;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
img{
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
top: unset;
|
||||
left: 15px;
|
||||
}
|
||||
.widget-list-item__follow-button{
|
||||
border: 2px solid transparent;
|
||||
border-radius: 3px;
|
||||
padding: 2px 8px;
|
||||
text-align: center;
|
||||
background-color: $green;
|
||||
color: $black;
|
||||
font-family: $helvetica-condensed;
|
||||
font-weight: 900;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,12 @@ module Api
|
|||
end
|
||||
end
|
||||
|
||||
def sidebar_suggestions
|
||||
given_tag = params[:tag]
|
||||
@users = UserFollowSuggester.new(nil).sidebar_suggestions(given_tag)
|
||||
render "index.json.jbuilder"
|
||||
end
|
||||
|
||||
def less_than_one_day_old?(user)
|
||||
range = (Time.now.beginning_of_day - 1.day)..(Time.now)
|
||||
user_identity_age = user.github_created_at ||
|
||||
|
|
|
|||
9
app/javascript/packs/sidebarWidget.jsx
Normal file
9
app/javascript/packs/sidebarWidget.jsx
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { h, render } from 'preact';
|
||||
import SidebarWidget from '../sidebar-widget/SidebarWidget';
|
||||
|
||||
HTMLDocument.prototype.ready = new Promise((resolve) => {
|
||||
if (document.readyState !== 'loading') { return resolve(); }
|
||||
document.addEventListener('DOMContentLoaded', () => resolve());
|
||||
});
|
||||
|
||||
render(<SidebarWidget />, document.getElementById('sidebarWidget__pack'));
|
||||
75
app/javascript/sidebar-widget/SidebarWidget.jsx
Normal file
75
app/javascript/sidebar-widget/SidebarWidget.jsx
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import { h, Component } from 'preact';
|
||||
import PropTypes from 'prop-types';
|
||||
import sendFollowUser from '../src/utils/sendFollowUser';
|
||||
import SidebarUser from './sidebarUser';
|
||||
|
||||
class SidebarWidget extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.getSuggestedUsers = this.getSuggestedUsers.bind(this);
|
||||
this.getTagInfo = this.getTagInfo.bind(this);
|
||||
this.followUser = this.followUser.bind(this);
|
||||
this.state = {
|
||||
tagInfo: {},
|
||||
suggestedUsers: [],
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getTagInfo();
|
||||
this.getSuggestedUsers();
|
||||
}
|
||||
|
||||
getTagInfo() {
|
||||
this.setState({ tagInfo: JSON.parse(document.getElementById('sidebarWidget__pack').dataset.tagInfo) });
|
||||
}
|
||||
|
||||
getSuggestedUsers() {
|
||||
fetch(`/api/users/sidebar_suggestions?tag=${this.state.tagInfo.name}`, {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then((json) => {
|
||||
this.setState({ suggestedUsers: json });
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
followUser(user) {
|
||||
const toggleFollowState = (newFollowState) => {
|
||||
const updatedUser = user;
|
||||
const updatedSuggestedUsers = this.state.suggestedUsers;
|
||||
const userIndex = this.state.suggestedUsers.indexOf(user);
|
||||
updatedUser.following = newFollowState === 'followed';
|
||||
updatedSuggestedUsers[userIndex] = updatedUser;
|
||||
this.setState({ suggestedUsers: updatedSuggestedUsers });
|
||||
};
|
||||
sendFollowUser(user, toggleFollowState);
|
||||
}
|
||||
|
||||
render() {
|
||||
const users = this.state.suggestedUsers.map((user) => {
|
||||
return(
|
||||
<SidebarUser key={user.id} user={user} followUser={this.followUser} />
|
||||
);
|
||||
});
|
||||
return (
|
||||
<div className="widget-suggested-follows-container">
|
||||
<header>
|
||||
{"<WHO TO FOLLOW>"}
|
||||
</header>
|
||||
<div className="widget-body">
|
||||
{users}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SidebarWidget;
|
||||
37
app/javascript/sidebar-widget/sidebarUser.jsx
Normal file
37
app/javascript/sidebar-widget/sidebarUser.jsx
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { h, Component } from 'preact';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
class SidebarUser extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onClick = this.onClick.bind(this);
|
||||
}
|
||||
|
||||
onClick() {
|
||||
this.props.followUser(this.props.user);
|
||||
}
|
||||
|
||||
render() {
|
||||
return(
|
||||
<div className="widget-list-item__suggestions">
|
||||
<div className="widget-list-item__content">
|
||||
<a href={`/${this.props.user.username}`}>
|
||||
<img src={this.props.user.profile_image_url} alt={this.props.user.name} className="widget-list-item__profile-pic" />
|
||||
{this.props.user.name}
|
||||
</a>
|
||||
<button className="widget-list-item__follow-button" type="button" onClick={this.onClick}>
|
||||
{this.props.user.following ? '✓ FOLLOWING' : '+ FOLLOW'}
|
||||
</button>
|
||||
</div>
|
||||
<hr />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
SidebarUser.propTypes = {
|
||||
followUser: PropTypes.func.isRequired,
|
||||
user: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default SidebarUser;
|
||||
|
|
@ -164,7 +164,6 @@ class Onboarding extends Component {
|
|||
formData.append('followable_type', 'Tag');
|
||||
formData.append('followable_id', tag.id);
|
||||
formData.append('verb', (tag.following ? 'unfollow' : 'follow'));
|
||||
formData.append('authenticity_token', csrfToken);
|
||||
|
||||
fetch('/follows', {
|
||||
method: 'POST',
|
||||
|
|
|
|||
25
app/javascript/src/utils/sendFollowUser.js
Normal file
25
app/javascript/src/utils/sendFollowUser.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
export default function sendFollowUser(user, successCb, failureCb) {
|
||||
const csrfToken = document.querySelector("meta[name='csrf-token']").content;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('followable_type', 'User');
|
||||
formData.append('followable_id', user.id);
|
||||
formData.append('verb', (user.following ? 'unfollow' : 'follow'));
|
||||
|
||||
fetch('/follows', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-Token': csrfToken,
|
||||
},
|
||||
body: formData,
|
||||
credentials: 'same-origin',
|
||||
}).then((response) => {
|
||||
return response.json();
|
||||
}).then((json) => {
|
||||
console.log('in fetch request: ' + json.outcome)
|
||||
successCb(json.outcome);
|
||||
// json is followed or unfollowed
|
||||
}).catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
|
@ -33,11 +33,20 @@ class UserFollowSuggester
|
|||
users
|
||||
end
|
||||
|
||||
def sidebar_suggestions(given_tag)
|
||||
user_ids = Article.tagged_with([given_tag], any: true).
|
||||
where("published = ? AND positive_reactions_count > ? AND published_at > ?",
|
||||
true, 15, 7.months.ago).
|
||||
pluck(:user_id)
|
||||
User.where(id: user_ids).order("reputation_modifier DESC").limit(3).to_a
|
||||
end
|
||||
|
||||
def tagged_article_user_ids
|
||||
Article.
|
||||
tagged_with(user.decorate.cached_followed_tag_names, any: true).
|
||||
where(published: true).
|
||||
where("positive_reactions_count > ? AND published_at > ?", article, 7.months.ago).pluck(:user_id).
|
||||
where("positive_reactions_count > ? AND published_at > ?", article, 7.months.ago).
|
||||
pluck(:user_id).
|
||||
each_with_object(Hash.new(0)) { |value, counts| counts[value] += 1 }.
|
||||
sort_by { |_key, value| value }.
|
||||
map { |arr| arr[0] }
|
||||
|
|
|
|||
|
|
@ -3,4 +3,5 @@ json.array! @users.each do |user|
|
|||
json.name user.name
|
||||
json.username user.username
|
||||
json.profile_image_url ProfileImage.new(user).get(90)
|
||||
json.following current_user ? current_user.following?(user) : false
|
||||
end
|
||||
|
|
|
|||
|
|
@ -57,6 +57,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= javascript_pack_tag "sidebarWidget", defer: true %>
|
||||
<div class="widget" id="sidebarWidget__pack" data-tag-info="<%= @tag_model.attributes.slice("id", "text_color_hex", "bg_color_hex", "name").to_json %>" />
|
||||
<% elsif @query %>
|
||||
<% elsif @podcast_index %>
|
||||
<% else %>
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
<% if (Flipflop.sendbird?) %>
|
||||
<div id="sb_chat" class="live-chat" data-sendbird-app-id="<%= ENV["SENDBIRD_APP_ID"] %>" data-sendbird-livechat-url="<%= ENV["SENDBIRD_LIVECHAT_URL"] %>"></div>
|
||||
<% else %>
|
||||
<div id="chat" class="live-chat" data-pusher-key="<%= ENV["PUSHER_KEY"] %>"</div>
|
||||
<div id="chat" class="live-chat" data-pusher-key="<%= ENV["PUSHER_KEY"] %>"></div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -50,7 +50,11 @@ Rails.application.routes.draw do
|
|||
post "/onboarding", to: "reactions#onboarding"
|
||||
end
|
||||
end
|
||||
resources :users, only: [:index]
|
||||
resources :users, only: [:index] do
|
||||
collection do
|
||||
get "/sidebar_suggestions", to: "users#sidebar_suggestions"
|
||||
end
|
||||
end
|
||||
resources :tags, only: [:index] do
|
||||
collection do
|
||||
get "/onboarding", to: "tags#onboarding"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue