Add messaging option in listings (#2854)
* add prop for message modal method * clean up comments from open inbox method * add inbox type to listing model author * add sample methods for opening message modal in listings and listing modal * only show for users with open inbox and hide for self * use contact_via_connect instead of inbox_type * fix edit listing to include contact via connect attribute * wip send message method * adjust message styling * change id name * update destination * remove unused methods
This commit is contained in:
parent
36ad407c1d
commit
19b46a86e2
10 changed files with 168 additions and 81 deletions
|
|
@ -60,13 +60,13 @@
|
|||
input {
|
||||
font-size: 0.8em;
|
||||
@include themeable(
|
||||
color,
|
||||
theme-color,
|
||||
color,
|
||||
theme-color,
|
||||
$black
|
||||
);
|
||||
@include themeable(
|
||||
background,
|
||||
theme-container-accent-background,
|
||||
background,
|
||||
theme-container-accent-background,
|
||||
$light-gray
|
||||
);
|
||||
border-radius: 100px;
|
||||
|
|
@ -126,19 +126,19 @@
|
|||
width: 100%;
|
||||
max-width: 580px;
|
||||
@include themeable(
|
||||
background,
|
||||
background,
|
||||
theme-container-background,
|
||||
white
|
||||
);
|
||||
box-sizing: border-box;
|
||||
@include themeable(
|
||||
border,
|
||||
theme-border,
|
||||
border,
|
||||
theme-border,
|
||||
1px solid $light-medium-gray
|
||||
);
|
||||
@include themeable(
|
||||
box-shadow,
|
||||
theme-container-box-shadow,
|
||||
box-shadow,
|
||||
theme-container-box-shadow,
|
||||
2px 2px 8px darken($light-medium-gray, 5%)
|
||||
);
|
||||
@media screen and (min-width: 580px) {
|
||||
|
|
@ -150,8 +150,8 @@
|
|||
}
|
||||
header {
|
||||
@include themeable(
|
||||
background,
|
||||
theme-container-accent-background,
|
||||
background,
|
||||
theme-container-accent-background,
|
||||
#f2f7fc
|
||||
);
|
||||
padding: 25px 22px;
|
||||
|
|
@ -173,8 +173,8 @@
|
|||
h2 {
|
||||
font-size: 1.45em;
|
||||
@include themeable(
|
||||
color,
|
||||
theme-color,
|
||||
color,
|
||||
theme-color,
|
||||
#32325d
|
||||
);
|
||||
margin: 0px;
|
||||
|
|
@ -203,13 +203,13 @@
|
|||
border-radius: 3px;
|
||||
border: 1px solid $light-medium-gray;
|
||||
@include themeable(
|
||||
background,
|
||||
theme-container-accent-background,
|
||||
background,
|
||||
theme-container-accent-background,
|
||||
$light-gray
|
||||
);
|
||||
@include themeable(
|
||||
color,
|
||||
theme-color,
|
||||
color,
|
||||
theme-color,
|
||||
$black
|
||||
);
|
||||
&[type="checkbox"] {
|
||||
|
|
@ -223,13 +223,13 @@
|
|||
font-size: 17px;
|
||||
border-radius: 3px;
|
||||
@include themeable(
|
||||
background,
|
||||
theme-container-accent-background,
|
||||
background,
|
||||
theme-container-accent-background,
|
||||
$light-gray
|
||||
);
|
||||
@include themeable(
|
||||
color,
|
||||
theme-color,
|
||||
color,
|
||||
theme-color,
|
||||
$black
|
||||
);
|
||||
border: 1px solid $light-medium-gray;
|
||||
|
|
@ -243,8 +243,8 @@
|
|||
font-size: 0.8em;
|
||||
padding: 8px 0px;
|
||||
@include themeable(
|
||||
color,
|
||||
theme-secondary-color,
|
||||
color,
|
||||
theme-secondary-color,
|
||||
$medium-gray
|
||||
);
|
||||
}
|
||||
|
|
@ -263,8 +263,8 @@
|
|||
margin: 35px auto;
|
||||
display: block;
|
||||
@include themeable(
|
||||
box-shadow,
|
||||
theme-container-box-shadow,
|
||||
box-shadow,
|
||||
theme-container-box-shadow,
|
||||
2px 2px 8px darken($light-medium-gray, 5%)
|
||||
);
|
||||
}
|
||||
|
|
@ -288,8 +288,8 @@
|
|||
text-decoration: underline;
|
||||
display: inline-block;
|
||||
@include themeable(
|
||||
color,
|
||||
theme-anchor-color,
|
||||
color,
|
||||
theme-anchor-color,
|
||||
$bold-blue
|
||||
);
|
||||
}
|
||||
|
|
@ -303,8 +303,8 @@
|
|||
p {
|
||||
font-size: 0.8em;
|
||||
@include themeable(
|
||||
color,
|
||||
theme-secondary-color,
|
||||
color,
|
||||
theme-secondary-color,
|
||||
$medium-gray
|
||||
);
|
||||
}
|
||||
|
|
@ -314,8 +314,8 @@
|
|||
button {
|
||||
background: transparent;
|
||||
@include themeable(
|
||||
border,
|
||||
theme-border,
|
||||
border,
|
||||
theme-border,
|
||||
1px solid $light-medium-gray
|
||||
);
|
||||
font-size: 17px;
|
||||
|
|
@ -339,14 +339,14 @@
|
|||
font-size: 17px;
|
||||
border-radius: 3px;
|
||||
@include themeable(
|
||||
background,
|
||||
theme-container-background,
|
||||
background,
|
||||
theme-container-background,
|
||||
white
|
||||
);
|
||||
margin-top: 10px;
|
||||
@include themeable(
|
||||
color,
|
||||
theme-color,
|
||||
color,
|
||||
theme-color,
|
||||
$black
|
||||
);
|
||||
border: 1px solid $light-medium-gray;
|
||||
|
|
@ -354,8 +354,8 @@
|
|||
|
||||
.listingform__tagsoptions {
|
||||
@include themeable(
|
||||
border,
|
||||
theme-border,
|
||||
border,
|
||||
theme-border,
|
||||
1px solid $light-medium-gray
|
||||
);
|
||||
border-bottom-left-radius: 3px;
|
||||
|
|
@ -389,6 +389,22 @@
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
.single-classified-listing-container {
|
||||
width: 90%;
|
||||
z-index: 20;
|
||||
top: 110px;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 12px auto;
|
||||
@media screen and (min-width: 950px) {
|
||||
top: calc( 50px + 15vh);
|
||||
left: 50%;
|
||||
width: 600px;
|
||||
margin-left: -300px; /* Negative half of width. */
|
||||
}
|
||||
}
|
||||
|
||||
.single-classified-listing {
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
|
|
@ -401,16 +417,16 @@
|
|||
font-size: 0.9em;
|
||||
break-inside: avoid;
|
||||
@include themeable(
|
||||
background,
|
||||
theme-container-background,
|
||||
background,
|
||||
theme-container-background,
|
||||
white
|
||||
);
|
||||
h3 {
|
||||
margin: 0px;
|
||||
padding: 9px 15px;
|
||||
@include themeable(
|
||||
background,
|
||||
theme-container-accent-background,
|
||||
background,
|
||||
theme-container-accent-background,
|
||||
lighten($light-medium-gray, 8%)
|
||||
);
|
||||
border-bottom: 1px solid $light-medium-gray;
|
||||
|
|
@ -435,13 +451,13 @@
|
|||
padding: 10px 15px;
|
||||
a {
|
||||
@include themeable(
|
||||
color,
|
||||
theme-secondary-color,
|
||||
color,
|
||||
theme-secondary-color,
|
||||
$dark-medium-gray
|
||||
);
|
||||
@include themeable(
|
||||
background,
|
||||
theme-container-accent-background,
|
||||
background,
|
||||
theme-container-accent-background,
|
||||
$light-medium-gray
|
||||
);
|
||||
display: inline-block;
|
||||
|
|
@ -455,15 +471,15 @@
|
|||
.single-classified-listing-author-info {
|
||||
font-size: 0.69em;
|
||||
@include themeable(
|
||||
color,
|
||||
theme-secondary-color,
|
||||
color,
|
||||
theme-secondary-color,
|
||||
$medium-gray
|
||||
);
|
||||
padding: 0px 15px 4px;
|
||||
a {
|
||||
@include themeable(
|
||||
color,
|
||||
theme-secondary-color,
|
||||
color,
|
||||
theme-secondary-color,
|
||||
$medium-gray
|
||||
);
|
||||
display: inline-block;
|
||||
|
|
@ -474,28 +490,57 @@
|
|||
}
|
||||
&.single-classified-listing--opened {
|
||||
z-index: 20;
|
||||
position: fixed;
|
||||
top: 110px;
|
||||
width: 90%;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
padding-bottom: 12px;
|
||||
@media screen and (min-width: 950px) {
|
||||
top: calc( 50px + 15vh);
|
||||
left: 50%;
|
||||
width: 600px;
|
||||
margin-left: -300px; /* Negative half of width. */
|
||||
h3 { font-size: calc(20px + 0.05vw);}
|
||||
.single-classified-listing-body {
|
||||
font-size: calc(18px + 0.05vw);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
form.listings-contact-via-connect {
|
||||
border-radius: 3px;
|
||||
z-index: 20;
|
||||
position: relative;
|
||||
padding: 10px 15px;
|
||||
margin: 12px auto;
|
||||
width: 100%;
|
||||
padding-bottom: 12px;
|
||||
box-shadow: none;
|
||||
max-width: 100%;
|
||||
text-align: right;
|
||||
@media screen and (min-width: 950px) {
|
||||
max-width: 600px;
|
||||
}
|
||||
textarea#new-message {
|
||||
height: 100px;
|
||||
margin: 5px auto 10px auto;
|
||||
font-size: 15px;
|
||||
@media screen and (min-width: 950px) {
|
||||
font-size: calc(18px + 0.05vw);
|
||||
}
|
||||
}
|
||||
.submit-button {
|
||||
margin: none 10px;
|
||||
padding: 0.5em;
|
||||
height: 2em;
|
||||
width: 4em;
|
||||
font-size: 1em;
|
||||
line-height: 1em;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.classified-listings-modal-background {
|
||||
z-index: 15;
|
||||
position: fixed;
|
||||
left: 0; top: 0; right: 0; bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,11 +90,10 @@ class ChatChannelsController < ApplicationController
|
|||
|
||||
def create_chat
|
||||
chat_recipient = User.find(params[:user_id])
|
||||
valid_listing = ClassifiedListing.where({ user_id: params[:user_id], contact_via_connect: true }).limit(1)
|
||||
authorize ChatChannel
|
||||
if chat_recipient.inbox_type == "open"
|
||||
if chat_recipient.inbox_type == "open" || valid_listing.length == 1
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ class ClassifiedListingsController < ApplicationController
|
|||
@classified_listing.title = params[:classified_listing][:title] if params[:classified_listing][:title]
|
||||
@classified_listing.body_markdown = params[:classified_listing][:body_markdown] if params[:classified_listing][:body_markdown]
|
||||
@classified_listing.tag_list = params[:classified_listing][:tag_list] if params[:classified_listing][:tag_list]
|
||||
@classified_listing.contact_via_connect = params[:classified_listing][:contact_via_connect] if params[:classified_listing][:contact_via_connect]
|
||||
@classified_listing.save
|
||||
end
|
||||
clear_listings_cache
|
||||
|
|
|
|||
|
|
@ -12,8 +12,9 @@ export class Listings extends Component {
|
|||
initialFetch: true,
|
||||
currentUserId: null,
|
||||
openedListing: null,
|
||||
message: '',
|
||||
slug: null,
|
||||
page: 0,
|
||||
page: 0,
|
||||
showNextPageButt: false,
|
||||
};
|
||||
|
||||
|
|
@ -108,12 +109,10 @@ export class Listings extends Component {
|
|||
}
|
||||
|
||||
handleCloseModal = (e) => {
|
||||
|
||||
const { query, tags, category } = this.state;
|
||||
this.setState({openedListing: null, page: 0})
|
||||
this.setLocation(query, tags, category, null);
|
||||
document.body.classList.remove('modal-open');
|
||||
|
||||
}
|
||||
|
||||
handleOpenModal = (e, listing) => {
|
||||
|
|
@ -124,6 +123,34 @@ export class Listings extends Component {
|
|||
document.body.classList.add('modal-open');
|
||||
}
|
||||
|
||||
handleDraftingMessage = (e) => {
|
||||
e.preventDefault();
|
||||
this.setState({ message: e.target.value })
|
||||
}
|
||||
|
||||
handleSubmitMessage = (e) => {
|
||||
if (this.state.message.replace(/\s/g, '').length === 0) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('user_id', this.state.openedListing.user_id);
|
||||
formData.append('message', this.state.message)
|
||||
formData.append('controller', 'chat_channels');
|
||||
|
||||
var destination = `/connect/@${this.state.openedListing.author.username}`;
|
||||
|
||||
getCsrfToken()
|
||||
.then(sendFetch('chat-creation', formData))
|
||||
.then(() => {
|
||||
window.location.href = destination;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
handleQuery = e => {
|
||||
const { tags, category } = this.state;
|
||||
this.setState({query: e.target.value, page: 0, listings: []})
|
||||
|
|
@ -156,7 +183,7 @@ export class Listings extends Component {
|
|||
loadNextPage = () => {
|
||||
const { query, tags, category, slug, page } = this.state;
|
||||
this.setState({page: page + 1});
|
||||
this.listingSearch(query, tags, category, slug);
|
||||
this.listingSearch(query, tags, category, slug);
|
||||
}
|
||||
|
||||
setUser = () => {
|
||||
|
|
@ -255,17 +282,29 @@ export class Listings extends Component {
|
|||
const clearQueryButton = query.length > 0 ? <button type="button" className='classified-search-clear' onClick={this.clearQuery}>×</button> : '';
|
||||
let modal = '';
|
||||
let modalBg = '';
|
||||
let messageModal = '';
|
||||
if (openedListing) {
|
||||
modalBg = <div className='classified-listings-modal-background' onClick={this.handleCloseModal} role='presentation' />
|
||||
if (openedListing.contact_via_connect && openedListing.user_id !== currentUserId) {
|
||||
messageModal = (
|
||||
<form id="listings-message-form" className="listings-contact-via-connect" onSubmit={this.handleSubmitMessage}>
|
||||
<textarea value={this.state.message} onChange={this.handleDraftingMessage} id="new-message" rows="4" cols="70" placeholder="Enter your message here..." />
|
||||
<button type="submit" value="Submit" className="submit-button cta">SEND</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
modal = (
|
||||
<SingleListing
|
||||
onAddTag={this.addTag}
|
||||
onChangeCategory={this.selectCategory}
|
||||
listing={openedListing}
|
||||
currentUserId={currentUserId}
|
||||
onOpenModal={this.handleOpenModal}
|
||||
isOpen
|
||||
/>
|
||||
<div className="single-classified-listing-container">
|
||||
<SingleListing
|
||||
onAddTag={this.addTag}
|
||||
onChangeCategory={this.selectCategory}
|
||||
listing={openedListing}
|
||||
currentUserId={currentUserId}
|
||||
onOpenModal={this.handleOpenModal}
|
||||
isOpen
|
||||
/>
|
||||
{messageModal}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
if (initialFetch) {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ export const SingleListing = ({listing, onAddTag, currentUserId, onChangeCategor
|
|||
));
|
||||
|
||||
const editButton = currentUserId === listing.user_id ? <a href={`/listings/${listing.id}/edit`} className="classified-listing-edit-button">・edit</a> : <a href={`/report-abuse?url=https://dev.to/listings/${listing.category}/${listing.slug}`}>・report abuse</a>;
|
||||
|
||||
const definedClass = isOpen ? 'single-classified-listing single-classified-listing--opened' : 'single-classified-listing';
|
||||
|
||||
const listingCard = () => {
|
||||
|
|
|
|||
|
|
@ -18,12 +18,13 @@ class ClassifiedListing < ApplicationRecord
|
|||
length: { maximum: 128 }
|
||||
validates :body_markdown, presence: true,
|
||||
length: { maximum: 400 }
|
||||
validate :contact_via_connect
|
||||
validate :restrict_markdown_input
|
||||
validate :validate_tags
|
||||
validate :validate_category
|
||||
|
||||
algoliasearch per_environment: true do
|
||||
attribute :title, :processed_html, :bumped_at, :tag_list, :category, :id, :user_id, :slug
|
||||
attribute :title, :processed_html, :bumped_at, :tag_list, :category, :id, :user_id, :slug, :contact_via_connect
|
||||
attribute :author do
|
||||
{ username: author.username,
|
||||
name: author.name,
|
||||
|
|
|
|||
|
|
@ -49,12 +49,10 @@
|
|||
</div>
|
||||
</div>
|
||||
<%= javascript_pack_tag "listingForm", defer: true %>
|
||||
<% if false # Put connect button (future feature) %>
|
||||
<div class="field">
|
||||
<%= form.label "contact_via_connect" %>
|
||||
<%= form.check_box "contact_via_connect" %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if current_user.organization && current_user.org_admin %>
|
||||
<div class="field">
|
||||
<%= form.label "post_as_organization", "Post as #{current_user.organization.name} organization" %>
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@
|
|||
<%= f.label "tag_list", "Tags" %>
|
||||
<%= f.text_field "tag_list", value: @classified_listing.cached_tag_list, placeholder: "8 tags max, comma separated, no spaces or special characters" %>
|
||||
</div>
|
||||
<div class="field">
|
||||
<%= f.label "contact_via_connect" %>
|
||||
<%= f.check_box "contact_via_connect" %>
|
||||
</div>
|
||||
<%= f.submit "Update Listings Info" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
|
@ -39,4 +43,4 @@
|
|||
<input type="hidden" name="classified_listing[action]" value="unpublish" />
|
||||
<%= f.submit "Unpublish Listing", class: "classified-listings-unpublish" %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
<div class="home">
|
||||
<div class="classifieds-container" id="classifieds-index-container"
|
||||
data-category="<%= params[:category] %>" data-listings="<%= @classified_listings.to_json(
|
||||
only: %i[title processed_html tag_list category id user_id slug],
|
||||
only: %i[title processed_html tag_list category id user_id slug contact_via_connect],
|
||||
include: {
|
||||
author: { only: %i[username name], methods: %i[username profile_image_90] },
|
||||
},
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
data-allcategories="<%= ClassifiedListing.categories_for_display.to_json %>"
|
||||
<% if @displayed_classified_listing %>
|
||||
data-displayedlisting="<%= @displayed_classified_listing.to_json(
|
||||
only: %i[title processed_html tag_list category id user_id slug],
|
||||
only: %i[title processed_html tag_list category id user_id slug contact_via_connect],
|
||||
include: {
|
||||
author: { only: %i[username name], methods: %i[username profile_image_90] },
|
||||
},
|
||||
|
|
|
|||
|
|
@ -252,6 +252,7 @@ listings_categories.each_with_index do |category, index|
|
|||
title: Faker::Lorem.sentence,
|
||||
body_markdown: Faker::Markdown.random,
|
||||
category: category,
|
||||
contact_via_connect: true,
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue