Skip to content

Commit 89826c7

Browse files
drusepthclaude
andcommitted
Redesign private messages, followers/following pages with Tailwind
- Fix collection card images by replacing unsupported /XX opacity shorthand with inline RGBA styles - Redesign private messages: compose form, index, new page with collapsible inline compose form using Alpine.js - Redesign followers/following pages with responsive grid layout and new user card partial - Add pagination to followers/following controller actions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent ba1cc45 commit 89826c7

File tree

9 files changed

+232
-147
lines changed

9 files changed

+232
-147
lines changed

app/controllers/users_controller.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,15 @@ def report_user_deletion_to_slack user
9999
end
100100

101101
def followers
102+
@followers = @user.followed_by_users
103+
.includes(:avatar_attachment, :thredded_user_detail)
104+
.paginate(page: params[:page], per_page: 100)
102105
end
103106

104107
def following
108+
@following = @user.followed_users
109+
.includes(:avatar_attachment, :thredded_user_detail)
110+
.paginate(page: params[:page], per_page: 100)
105111
end
106112

107113
def tag

app/views/thredded/private_topics/_form.html.erb

Lines changed: 41 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,58 +8,63 @@
88
'data-thredded-submit-hotkey' => true,
99
} do |form| %>
1010

11-
<ul class="thredded--form-list on-top">
12-
<li class="title">
13-
<%= form.label :title, t('thredded.private_topics.form.title_label') %>
14-
<%= form.text_field :title, placeholder: placeholder, required: true, autocomplete: 'off' %>
11+
<div class="space-y-4">
12+
<!-- Title Field -->
13+
<div>
14+
<%= form.label :title, t('thredded.private_topics.form.title_label'), class: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" %>
15+
<%= form.text_field :title,
16+
placeholder: placeholder,
17+
required: true,
18+
autocomplete: 'off',
19+
class: "w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white text-sm" %>
1520
<%= render 'thredded/shared/field_errors', messages: form.object.errors[:title] %>
16-
</li>
17-
18-
<li>
19-
<%= form.label :user_names, t('thredded.private_topics.form.users_label') %>
21+
</div>
22+
23+
<!-- Participants Field -->
24+
<div>
25+
<%= form.label :user_names, t('thredded.private_topics.form.users_label'), class: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" %>
2026
<%= form.text_area :user_names,
2127
required: true,
2228
placeholder: t('thredded.private_topics.form.users_placeholder'),
2329
'data-thredded-users-select' => true,
2430
'data-autocomplete-url' => autocomplete_users_path,
2531
'data-autocomplete-min-length' => Thredded.autocomplete_min_length,
26-
rows: 1 %>
27-
<div class="recipients-helper">
28-
<i class="material-icons">info</i>
32+
rows: 1,
33+
class: "w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white text-sm" %>
34+
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400 flex items-center">
35+
<i class="material-icons text-xs mr-1">info</i>
2936
Start typing to search for users
30-
</div>
37+
</p>
3138
<%= render 'thredded/shared/field_errors', messages: form.object.errors[:user_names] %>
32-
</li>
39+
</div>
3340

41+
<!-- Message Content -->
3442
<%= render 'thredded/posts_common/form/content',
3543
form: form,
3644
content_label: t('thredded.private_topics.form.content_label'),
3745
preview_url: private_topic.preview_path %>
3846

39-
<li>
40-
<div class="compose-actions">
41-
<div class="left-actions">
42-
<a href="<%= private_topics_path %>" class="btn-cancel">
43-
<i class="material-icons">arrow_back</i>
44-
Cancel
45-
</a>
46-
</div>
47-
48-
<div class="right-actions">
49-
<button type="button" class="btn-preview" data-thredded-preview-toggle>
50-
<i class="material-icons">visibility</i>
51-
Preview
52-
</button>
53-
54-
<button type="submit" class="thredded--form--submit"
55-
data-disable-with="<%= t 'thredded.private_topics.form.create_btn_submitting' %>">
56-
<i class="material-icons">send</i>
57-
<%= t('thredded.private_topics.form.create_btn') %>
58-
</button>
59-
</div>
47+
<!-- Actions -->
48+
<div class="flex items-center justify-between pt-4 border-t border-gray-200 dark:border-gray-700">
49+
<a href="<%= private_topics_path %>" class="inline-flex items-center px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors">
50+
<i class="material-icons text-sm mr-1">arrow_back</i>
51+
Cancel
52+
</a>
53+
54+
<div class="flex items-center space-x-3">
55+
<button type="button" class="inline-flex items-center px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors" data-thredded-preview-toggle>
56+
<i class="material-icons text-sm mr-1">visibility</i>
57+
Preview
58+
</button>
59+
60+
<button type="submit" class="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors"
61+
data-disable-with="<%= t 'thredded.private_topics.form.create_btn_submitting' %>">
62+
<i class="material-icons text-sm mr-1">send</i>
63+
<%= t('thredded.private_topics.form.create_btn') %>
64+
</button>
6065
</div>
61-
</li>
62-
</ul>
66+
</div>
67+
</div>
6368
<% end %>
6469

6570
<script>
Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,37 @@
1-
<div class="flex flex-col items-center justify-center py-16 px-4 text-center bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm">
2-
<div class="bg-blue-50 dark:bg-blue-900/20 p-4 rounded-full mb-4">
3-
<i class="material-icons text-4xl text-blue-500 dark:text-blue-400">mail_outline</i>
1+
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm" x-data="{ showComposeForm: false }">
2+
<!-- Empty State -->
3+
<div x-show="!showComposeForm" class="flex flex-col items-center justify-center py-12 px-4 text-center">
4+
<div class="bg-blue-50 dark:bg-blue-900/20 p-4 rounded-full mb-4">
5+
<i class="material-icons text-4xl text-blue-500 dark:text-blue-400">mail_outline</i>
6+
</div>
7+
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">No messages yet</h3>
8+
<p class="text-gray-500 dark:text-gray-400 max-w-sm mb-6">Start a private conversation with other members of the community.</p>
9+
<button @click="showComposeForm = true" class="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors">
10+
<i class="material-icons mr-2 -ml-1">edit</i>
11+
<%= t 'thredded.private_topics.no_private_topics.create_btn' %>
12+
</button>
13+
</div>
14+
15+
<!-- Compose Form (shown when button clicked) -->
16+
<div x-show="showComposeForm"
17+
x-transition:enter="transition ease-out duration-200"
18+
x-transition:enter-start="opacity-0"
19+
x-transition:enter-end="opacity-100"
20+
class="p-6">
21+
<div class="flex items-center justify-between mb-4">
22+
<h3 class="text-lg font-medium text-gray-900 dark:text-white flex items-center">
23+
<i class="material-icons mr-2 text-blue-600 dark:text-blue-400">mail_outline</i>
24+
New Message
25+
</h3>
26+
<button @click="showComposeForm = false" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
27+
<i class="material-icons">close</i>
28+
</button>
29+
</div>
30+
<% if @new_private_topic %>
31+
<%= render 'thredded/private_topics/form',
32+
private_topic: @new_private_topic,
33+
css_class: 'thredded--is-compact',
34+
placeholder: t('thredded.private_topics.form.title_placeholder_start') %>
35+
<% end %>
436
</div>
5-
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">No messages yet</h3>
6-
<p class="text-gray-500 dark:text-gray-400 max-w-sm mb-6">Start a private conversation with other members of the community.</p>
7-
<a href="<%= new_private_topic_path %>" class="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors">
8-
<i class="material-icons mr-2 -ml-1">edit</i>
9-
<%= t 'thredded.private_topics.no_private_topics.create_btn' %>
10-
</a>
1137
</div>

app/views/thredded/private_topics/index.html.erb

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@
33
<% content_for :thredded_breadcrumbs, render('thredded/private_topics/breadcrumbs') %>
44

55
<%= thredded_page do %>
6-
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
6+
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8" x-data="{ showComposeForm: false }">
77
<!-- Page Header -->
88
<div class="flex flex-col md:flex-row md:items-center md:justify-between mb-8">
99
<div class="flex-1 min-w-0">
1010
<h1 class="text-2xl font-bold text-gray-900 dark:text-white flex items-center">
1111
<i class="material-icons mr-3 text-blue-600 dark:text-blue-400">mail</i>
1212
Private Messages
1313
</h1>
14-
14+
1515
<% if @private_topics.to_a.any? %>
1616
<div class="mt-2 flex items-center text-sm text-gray-500 dark:text-gray-400 space-x-4">
1717
<div class="flex items-center">
@@ -28,12 +28,13 @@
2828
</div>
2929
<% end %>
3030
</div>
31-
31+
3232
<div class="mt-4 md:mt-0 flex md:ml-4">
33-
<a href="<%= new_private_topic_path %>" class="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors">
34-
<i class="material-icons mr-2 -ml-1">edit</i>
35-
Compose
36-
</a>
33+
<button @click="showComposeForm = !showComposeForm"
34+
class="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors">
35+
<i class="material-icons mr-2 -ml-1" x-text="showComposeForm ? 'close' : 'edit'"></i>
36+
<span x-text="showComposeForm ? 'Cancel' : 'Compose'"></span>
37+
</button>
3738
</div>
3839
</div>
3940

@@ -47,7 +48,14 @@
4748
'data-thredded-topic-posts-per-page' => Thredded.posts_per_page do %>
4849

4950
<% if @new_private_topic %>
50-
<div class="p-4 border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900">
51+
<div x-show="showComposeForm"
52+
x-transition:enter="transition ease-out duration-200"
53+
x-transition:enter-start="opacity-0 -translate-y-2"
54+
x-transition:enter-end="opacity-100 translate-y-0"
55+
x-transition:leave="transition ease-in duration-150"
56+
x-transition:leave-start="opacity-100 translate-y-0"
57+
x-transition:leave-end="opacity-0 -translate-y-2"
58+
class="p-4 border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900">
5159
<%= render 'thredded/private_topics/form',
5260
private_topic: @new_private_topic,
5361
css_class: 'thredded--is-compact',

app/views/thredded/private_topics/new.html.erb

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,21 @@
33
<% content_for :thredded_breadcrumbs, render('thredded/private_topics/breadcrumbs') %>
44

55
<%= thredded_page do %>
6-
<div class="compose-container">
7-
<div class="compose-wrapper">
8-
<!-- Compose Header -->
9-
<div class="compose-header">
10-
<h1>
11-
<i class="material-icons">mail_outline</i>
12-
Compose Private Message
13-
</h1>
14-
<p class="compose-subtitle">Start a private conversation with other members</p>
15-
</div>
16-
17-
<!-- Compose Form -->
18-
<section class="thredded--main-section">
19-
<%= render 'thredded/private_topics/form',
20-
private_topic: @private_topic,
21-
placeholder: t('thredded.private_topics.form.title_placeholder_new') if @private_topic %>
22-
</section>
6+
<div class="max-w-2xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
7+
<!-- Compose Header -->
8+
<div class="mb-6">
9+
<h1 class="text-xl font-bold text-gray-900 dark:text-white flex items-center">
10+
<i class="material-icons mr-2 text-blue-600 dark:text-blue-400">mail_outline</i>
11+
Compose Private Message
12+
</h1>
13+
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Start a private conversation with other members</p>
14+
</div>
15+
16+
<!-- Compose Form -->
17+
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm p-6">
18+
<%= render 'thredded/private_topics/form',
19+
private_topic: @private_topic,
20+
placeholder: t('thredded.private_topics.form.title_placeholder_new') if @private_topic %>
2321
</div>
2422
</div>
2523
<% end %>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<div class="h-full">
2+
<%= link_to user, class: "group block h-full" do %>
3+
<div class="h-full bg-white dark:bg-gray-800 rounded-lg shadow-sm hover:shadow-md border border-gray-200 dark:border-gray-700 transition-all duration-200 overflow-hidden flex flex-row">
4+
<div class="w-24 sm:w-32 flex-shrink-0 bg-gray-100 dark:bg-gray-900 relative">
5+
<%= image_tag user.image_url(size: 200).html_safe, class: "w-full h-full object-cover absolute inset-0", loading: "lazy", alt: user.display_name %>
6+
</div>
7+
<div class="flex-1 p-3 sm:p-4 flex flex-col justify-center min-w-0">
8+
<div class="flex items-center gap-2 mb-1">
9+
<h3 class="font-bold text-lg text-gray-900 dark:text-gray-100 group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors truncate">
10+
<%= user.display_name %>
11+
</h3>
12+
</div>
13+
14+
<div class="text-sm">
15+
<%= render partial: 'thredded/users/badge', locals: { user: user } %>
16+
</div>
17+
18+
<% if user.bio.present? %>
19+
<p class="text-sm text-gray-600 dark:text-gray-400 mt-2 line-clamp-2">
20+
<%= user.bio %>
21+
</p>
22+
<% else %>
23+
<p class="text-sm text-gray-500 dark:text-gray-500 mt-2 italic">
24+
No bio available.
25+
</p>
26+
<% end %>
27+
</div>
28+
</div>
29+
<% end %>
30+
</div>

app/views/users/followers.html.erb

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -16,48 +16,54 @@
1616
</script>
1717

1818
<%= content_for :full_width_page_header do %>
19-
<div class="user-profile-ui card">
20-
<div class="card-image">
19+
<div class="relative w-full bg-gray-100 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
20+
<div class="h-48 w-full relative group overflow-hidden">
2121
<% if @user.favorite_page_type? %>
22-
<div class="<%= @accent_color %> darken-4" style="height: 200px; width: 100%"></div>
22+
<div class="<%= @user.favorite_page_type_color %> w-full h-full bg-opacity-90 dark:bg-opacity-80" style="background-color: <%= @user.favorite_page_type_color %>"></div>
23+
<div class="absolute inset-0 bg-gradient-to-t from-black/50 to-transparent"></div>
2324
<% else %>
24-
<%= image_tag "card-headers/users.png", style: 'height: 200px;' %>
25+
<%= image_tag "card-headers/users.png", class: "w-full h-full object-cover", alt: "User profile header" %>
26+
<div class="absolute inset-0 bg-gradient-to-t from-black/50 to-transparent"></div>
2527
<% end %>
26-
<span class="card-title">
27-
<%= image_tag @user.image_url.html_safe, class: 'header-avatar materialboxed', data: { caption: "#{@user.display_name}'s avatar" } %>
28-
29-
<%= link_to @user.name, @user, class: 'white-text' %>
30-
<span class="black-text">
31-
<%= render partial: 'thredded/users/badge', locals: { user: @user } %>
32-
</span>
33-
</span>
34-
</div>
35-
</div>
36-
<% end %>
37-
38-
<h5 class="grey-text">Followed by <%= pluralize @user.followed_by_users.count, 'worldbuilder' %></h5>
39-
<div class="row">
40-
<% @user.followed_by_users.each do |user| %>
41-
<div class="col s12 m12 l6">
42-
<%= link_to user, class: 'black-text' do %>
43-
<div class="hoverable card horizontal">
44-
<div class="card-image">
45-
<%= image_tag user.image_url.html_safe, class: 'header-avatar' %>
28+
29+
<div class="absolute bottom-0 left-0 p-4 md:p-6 w-full flex items-end">
30+
<div class="flex items-center gap-4">
31+
<div class="relative">
32+
<%= image_tag @user.image_url.html_safe, class: "w-20 h-20 md:w-24 md:h-24 rounded-full border-4 border-white dark:border-gray-900 bg-white dark:bg-gray-900 shadow-lg object-cover", data: { caption: "#{@user.display_name}'s avatar" } %>
4633
</div>
47-
<div class="card-content">
48-
<div class="card-title">
49-
<%= user.display_name %>
50-
<br />
51-
<span class="black-text">
52-
<%= render partial: 'thredded/users/badge', locals: { user: user } %>
53-
</span>
34+
35+
<div class="mb-2">
36+
<h1 class="text-2xl md:text-3xl font-bold text-white drop-shadow-md">
37+
<%= link_to @user.name, @user, class: "text-white hover:text-gray-200 transition-colors" %>
38+
</h1>
39+
<div class="text-white/90 drop-shadow-sm font-medium">
40+
<%= render partial: 'thredded/users/badge', locals: { user: @user } %>
5441
</div>
55-
<p>
56-
<%= truncate(user.bio, length: 140) %>
57-
</p>
5842
</div>
5943
</div>
60-
<% end %>
44+
</div>
45+
</div>
46+
</div>
47+
<% end %>
48+
49+
<div class="container mx-auto px-4 py-8">
50+
<div class="mb-6">
51+
<h2 class="text-2xl font-bold text-gray-800 dark:text-gray-100 flex items-center gap-2">
52+
<i class="material-icons text-gray-400">group</i>
53+
<span>Followed by</span>
54+
<span class="text-gray-400 text-lg font-normal ml-2">
55+
<%= pluralize @user.followed_by_users.count, 'worldbuilder' %>
56+
</span>
57+
</h2>
58+
</div>
59+
60+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
61+
<%= render partial: 'users/user_card', collection: @followers, as: :user %>
62+
</div>
63+
64+
<% if @followers.respond_to?(:total_pages) && @followers.total_pages > 1 %>
65+
<div class="mt-8 flex justify-center">
66+
<%= will_paginate @followers %>
6167
</div>
6268
<% end %>
6369
</div>

0 commit comments

Comments
 (0)