Skip to content

Commit 538ccf1

Browse files
authored
Improvement/conversation actions and UI (#1041)
- adds update action to conversations controller - extracts conversation form into a partial for reusability - adds dropdown menu on conversation header for managing conversation - updates to conversation_policy
2 parents c174c95 + 1d2496c commit 538ccf1

File tree

12 files changed

+216
-62
lines changed

12 files changed

+216
-62
lines changed

app/controllers/better_together/conversations_controller.rb

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22

33
module BetterTogether
44
# Handles managing conversations
5-
class ConversationsController < ApplicationController
5+
class ConversationsController < ApplicationController # rubocop:todo Metrics/ClassLength
66
before_action :authenticate_user!
77
before_action :disallow_robots
88
before_action :set_conversations, only: %i[index new show]
9-
before_action :set_conversation, only: %i[show]
9+
before_action :set_conversation, only: %i[show update leave_conversation]
10+
after_action :verify_authorized
1011

1112
layout 'better_together/conversation', only: %i[show]
1213

@@ -47,6 +48,54 @@ def create # rubocop:todo Metrics/MethodLength, Metrics/AbcSize
4748
end
4849
end
4950

51+
def update # rubocop:todo Metrics/AbcSize, Metrics/MethodLength
52+
authorize @conversation
53+
ActiveRecord::Base.transaction do # rubocop:todo Metrics/BlockLength
54+
if @conversation.update(conversation_params)
55+
@messages = @conversation.messages.with_all_rich_text.includes(sender: [:string_translations])
56+
.order(:created_at)
57+
@message = @conversation.messages.build
58+
59+
is_current_user_in_conversation = @conversation.participant_ids.include?(helpers.current_person.id)
60+
61+
turbo_stream_response = lambda do
62+
if is_current_user_in_conversation
63+
render turbo_stream: turbo_stream.replace(
64+
helpers.dom_id(@conversation),
65+
partial: 'better_together/conversations/conversation_content',
66+
locals: { conversation: @conversation, messages: @messages, message: @message }
67+
)
68+
else
69+
render turbo_stream: turbo_stream.action(:full_page_redirect, conversations_path)
70+
end
71+
end
72+
73+
html_response = lambda do
74+
if is_current_user_in_conversation
75+
redirect_to @conversation
76+
else
77+
redirect_to conversations_path
78+
end
79+
end
80+
81+
respond_to do |format|
82+
format.turbo_stream { turbo_stream_response.call }
83+
format.html { html_response.call }
84+
end
85+
else
86+
respond_to do |format|
87+
format.turbo_stream do
88+
render turbo_stream: turbo_stream.update(
89+
'form_errors',
90+
partial: 'layouts/better_together/errors',
91+
locals: { object: @conversation }
92+
)
93+
end
94+
end
95+
end
96+
end
97+
end
98+
5099
def show # rubocop:todo Metrics/MethodLength, Metrics/AbcSize
51100
authorize @conversation
52101

@@ -67,12 +116,35 @@ def show # rubocop:todo Metrics/MethodLength, Metrics/AbcSize
67116
render turbo_stream: turbo_stream.replace(
68117
'conversation_content',
69118
partial: 'better_together/conversations/conversation_content',
70-
locals: { conversation: @conversation, messages: @messages }
119+
locals: { conversation: @conversation, messages: @messages, message: @message }
71120
)
72121
end
73122
end
74123
end
75124

125+
def leave_conversation # rubocop:todo Metrics/MethodLength, Metrics/AbcSize
126+
authorize @conversation
127+
128+
flash[:error] if @conversation.participant_ids.size == 1
129+
130+
participant = @conversation.conversation_participants.find_by(person: helpers.current_person)
131+
132+
if participant.destroy
133+
redirect_to conversations_path, notice: t('better_together.conversations.conversation.left',
134+
conversation: @conversation.title)
135+
else
136+
respond_to do |format|
137+
format.turbo_stream do
138+
render turbo_stream: turbo_stream.update(
139+
'form_errors',
140+
partial: 'layouts/better_together/errors',
141+
locals: { object: @conversation }
142+
)
143+
end
144+
end
145+
end
146+
end
147+
76148
private
77149

78150
def available_participants

app/controllers/better_together/pages_controller.rb

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,6 @@ def destroy
107107
def id_param
108108
path = params[:path]
109109

110-
# if path.nil?
111-
# I18n.locale = I18n.default_locale
112-
# id_param = 'home-page'
113-
# end
114-
115110
path.present? ? path : super
116111
end
117112

app/models/better_together/conversation.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,18 @@ class Conversation < ApplicationRecord
88
has_many :messages, dependent: :destroy
99
has_many :conversation_participants, dependent: :destroy
1010
has_many :participants, through: :conversation_participants, source: :person
11+
validate :at_least_one_participant
1112

1213
def to_s
1314
title
1415
end
16+
17+
private
18+
19+
def at_least_one_participant
20+
return unless participants.empty?
21+
22+
errors.add(:conversation_participants, I18n.t('pundit.errors.leave_conversation'))
23+
end
1524
end
1625
end

app/policies/better_together/conversation_policy.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,18 @@ def create?
1111
user.present?
1212
end
1313

14+
def update?
15+
user.present? && record.creator == agent
16+
end
17+
1418
def show?
1519
user.present? && record.participants.include?(agent)
1620
end
1721

22+
def leave_conversation?
23+
user.present? && record.participants.size > 1
24+
end
25+
1826
class Scope < ApplicationPolicy::Scope
1927
end
2028
end
Lines changed: 71 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,75 @@
11
<!-- app/views/conversations/_conversation_content.html.erb -->
2-
<div id="<%= dom_id(conversation) %>" class="conversation-details card shadow-sm d-flex flex-column justify-content-between">
3-
<div class="card-header bg-secondary text-white text-center d-flex align-items-center px-2 rounded-0">
4-
<a class="text-decoration-none align-self-start p-4" href="<%= conversations_path %>">
5-
<i class="fas fa-arrow-left fa-2x "></i>
6-
</a>
7-
<% if conversation.title.present? %>
8-
<h4 class="m-0 me-4 align-self-center">
9-
<%= conversation.title %>
10-
</h4>
11-
<% end %>
12-
<div class="conversation-participants w-100 d-flex align-items-top justify-content-evenly overflow-auto">
13-
<%= render partial: 'better_together/people/mention', collection: conversation.participants, as: :person, locals: { flex_layout: 'flex-column', flex_align_items: 'center' } %>
14-
</div>
15-
</div>
2+
<div id="<%= dom_id(conversation) %>"
3+
class="conversation-details card shadow-sm d-flex flex-column justify-content-between">
4+
<div class="position-relative">
5+
<div class="card-header bg-secondary text-white text-center d-flex align-items-center px-2 rounded-0">
6+
<a class="text-decoration-none align-self-start p-4" href="<%= conversations_path %>">
7+
<i class="fas fa-arrow-left fa-2x"></i>
8+
</a>
9+
<% if conversation.title.present? %>
10+
<h4 class="m-0 me-4 align-self-center">
11+
<%= conversation.title %>
12+
</h4>
13+
<% end %>
14+
<div class="conversation-participants w-100 d-flex align-items-top justify-content-evenly overflow-auto">
15+
<%= render partial: 'better_together/people/mention' , collection: conversation.participants, as: :person,
16+
locals: { flex_layout: 'flex-column' , flex_align_items: 'center' } %>
17+
</div>
18+
<div data-bs-toggle="tooltip" title="<%= t('.options_tooltip') %>" class="align-self-center mx-4">
19+
<a class="text-white" href="#" data-bs-toggle="dropdown">
20+
<i class="fas fa-xl fa-ellipsis"></i>
21+
</a>
22+
<ul class="dropdown-menu p-2">
23+
<% if policy(@conversation).update? %>
24+
<li>
25+
<button class="btn" data-bs-toggle="collapse" data-bs-target="#edit_conversation">
26+
<i class="fas fa-pencil me-1"></i>
27+
<%= t('.edit_conversation') %>
28+
</button>
29+
</li>
30+
<% end %>
31+
<% if policy(@conversation).leave_conversation? %>
32+
<li>
33+
<%= button_to leave_conversation_conversation_path(conversation), method: :put, class: 'btn' do %>
34+
<i class="fas fa-arrow-right-from-bracket me-1"></i>
35+
<%= t('.leave_conversation') %>
36+
<% end %>
37+
</li>
38+
<% end %>
39+
<%# TODO: Implement conversation owner can delete/archive a conversation %>
40+
<!-- <li>
41+
<hr class="dropdown-divider">
42+
</li>
43+
<li>
44+
<button class="btn text-danger">
45+
<i class="fas fa-trash-can me-1"></i>
46+
Delete Conversation
47+
</button>
48+
</li> -->
49+
</ul>
50+
</div>
51+
</div>
52+
<div id="edit_conversation"
53+
class="collapse bg-secondary text-white px-2 py-3 position-absolute top-100 w-100 rounded-bottom"
54+
style="z-index: 100;">
55+
<div class="w-100 d-flex justify-content-end">
56+
<button class="btn" data-bs-toggle="collapse" data-bs-target="#edit_conversation">
57+
<i class="fas fa-xmark"></i>
58+
</button>
59+
</div>
60+
<%= render "form" , conversation: @conversation %>
61+
</div>
62+
</div>
1663

17-
<%= turbo_stream_from conversation %>
64+
<%= turbo_stream_from conversation %>
1865

19-
<div id="conversation_messages" class="card-body p-4" data-controller="better_together--conversation-messages" data-better_together--conversation-messages-current-person-id-value="<%= current_person.id %>">
20-
<%= render(partial: 'better_together/messages/message', collection: messages, as: :message, locals: {read_status: 'read'}) || render(partial: 'better_together/conversations/empty', locals: { conversation: }) %>
21-
</div>
66+
<div id="conversation_messages" class="card-body p-4" data-controller="better_together--conversation-messages"
67+
data-better_together--conversation-messages-current-person-id-value="<%= current_person.id %>">
68+
<%= render(partial: 'better_together/messages/message' , collection: messages, as: :message, locals:
69+
{read_status: 'read' }) || render(partial: 'better_together/conversations/empty' , locals: { conversation: }) %>
70+
</div>
2271

23-
<div class="card-footer">
24-
<%= render partial: 'better_together/messages/form', locals: { conversation: conversation, message: message } %>
25-
</div>
26-
</div>
72+
<div class="card-footer">
73+
<%= render partial: 'better_together/messages/form' , locals: { conversation: conversation, message: message } %>
74+
</div>
75+
</div>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<%= form_with(model: conversation, data: { turbo: false, controller: 'better_together--form-validation' }) do |form| %>
2+
<%= turbo_frame_tag 'form_errors' %>
3+
4+
<div class="mb-3">
5+
<%= form.label :participant_ids, t('.add_participants') %>
6+
<%= form.collection_select :participant_ids, available_participants, :id, :select_option_title, {}, { multiple:
7+
true, class: 'form-select' , required: true, data: { controller: 'better_together--slim-select' } } %>
8+
</div>
9+
10+
<div class="mb-3">
11+
<%= form.label :title %>
12+
<%= form.text_field :title, class: 'form-control' %>
13+
</div>
14+
15+
<div>
16+
<%= form.submit class: 'btn btn-sm btn-primary' %>
17+
</div>
18+
<% end %>
Lines changed: 7 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,13 @@
11
<%= render layout: 'communicator' do %>
2-
<div id="new_conversation_form" class="conversation-messages card shadow-sm">
3-
<div class="card-header rounded-0 bg-primary text-white">
4-
<h6 class="mb-0"><%= t('.new_conversation') %></h6>
5-
</div>
2+
<div id="new_conversation_form" class="conversation-messages card shadow-sm">
3+
<div class="card-header rounded-0 bg-primary text-white">
4+
<h6 class="mb-0">
5+
<%= t('.new_conversation') %>
6+
</h6>
7+
</div>
68

79
<div class="card-body">
8-
<%= form_with(model: @conversation, data: { controller: 'better_together--form-validation' }) do |form| %>
9-
<%= turbo_frame_tag 'form_errors' %>
10-
11-
<div class="mb-3">
12-
<%= form.label :participant_ids, t('.add_participants') %>
13-
<%= form.collection_select :participant_ids,
14-
available_participants,
15-
:id, :select_option_title,
16-
{},
17-
{
18-
multiple: true,
19-
class: 'form-select',
20-
required: true,
21-
data: { controller: 'better_together--slim-select' }
22-
} %>
23-
</div>
24-
25-
<div class="mb-3">
26-
<%= form.label :title %>
27-
<%= form.text_field :title, class: 'form-control' %>
28-
</div>
29-
30-
<div>
31-
<%= form.submit t('.create_conversation'), class: 'btn btn-sm btn-primary' %>
32-
</div>
33-
<% end %>
10+
<%= render 'form', conversation: @conversation %>
3411
</div>
3512
</div>
3613
<% end %>

config/locales/en.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -665,8 +665,15 @@ en:
665665
new_conversation: New Conversation
666666
conversation:
667667
last_message: Last message
668+
left: You have left the conversation %{conversation}
669+
conversation_content:
670+
edit_conversation: Edit Conversation
671+
leave_conversation: Leave Conversation
672+
options_tooltip: Conversation Options
668673
empty:
669674
no_messages: No messages yet. Why not start the conversation?
675+
form:
676+
create_conversation: Create conversation
670677
index:
671678
conversations: Conversations
672679
new: New
@@ -686,7 +693,6 @@ en:
686693
new_conversation: New Conversation
687694
new:
688695
add_participants: Add participants
689-
create_conversation: Create conversation
690696
new_conversation: New conversation
691697
sidebar:
692698
conversations: Conversations
@@ -1496,6 +1502,7 @@ en:
14961502
destroy: You are not authorized to delete this %{resource}.
14971503
edit: You are not authorized to edit this %{resource}.
14981504
index: You are not authorized to view the list of %{resource}.
1505+
leave_conversation: You are the final conversation participant and cannot leave
14991506
new: You are not authorized to create a new %{resource}.
15001507
show: You are not authorized to view this %{resource}.
15011508
update: You are not authorized to update this %{resource}.

config/locales/es.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,11 @@ es:
678678
new_conversation: New Conversation
679679
conversation:
680680
last_message: Último mensaje
681+
left: Has abandonado la conversación %{conversation}
682+
conversation_content:
683+
edit_conversation: Editar Conversación
684+
leave_conversation: Dejar la Conversación
685+
options_tooltip: Opciones de Conversación
681686
empty:
682687
no_messages: Aún no hay mensajes. ¿Por qué no iniciar la conversación?
683688
index:
@@ -1485,6 +1490,7 @@ es:
14851490
destroy: No está autorizado para eliminar este %{resource}.
14861491
edit: No está autorizado para editar este %{resource}.
14871492
index: No está autorizado para ver la lista de %{resource}.
1493+
leave_conversation: You are the final conversation participant and cannot leave
14881494
new: No está autorizado para crear un nuevo %{resource}.
14891495
show: No está autorizado para ver este %{resource}.
14901496
update: No está autorizado para actualizar este %{resource}.

config/locales/fr.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,11 @@ fr:
680680
new_conversation: New Conversation
681681
conversation:
682682
last_message: Dernier message
683+
left: Vous avez quitté la conversation %{conversation}
684+
conversation_content:
685+
edit_conversation: Modifier la Conversation
686+
leave_conversation: Quitter la Conversation
687+
options_tooltip: Options de Conversation
683688
empty:
684689
no_messages: Pas encore de messages. Pourquoi ne pas commencer la conversation
685690
?
@@ -1507,6 +1512,7 @@ fr:
15071512
destroy: Vous n'êtes pas autorisé à supprimer ce %{resource}.
15081513
edit: Vous n'êtes pas autorisé à modifier ce %{resource}.
15091514
index: Vous n'êtes pas autorisé à consulter la liste des %{resource}.
1515+
leave_conversation: You are the final conversation participant and cannot leave
15101516
new: Vous n'êtes pas autorisé à créer un nouveau %{resource}.
15111517
show: Vous n'êtes pas autorisé à consulter ce %{resource}.
15121518
update: Vous n'êtes pas autorisé à mettre à jour ce %{resource}.

0 commit comments

Comments
 (0)