mirror of
https://github.com/System-End/hackatime.git
synced 2026-04-19 22:15:14 +00:00
feat: add recent signup users with avatars (#89)
* feat: add recent signup users with avatars - Show avatars of recent Hackatime setup users - Add a hoverable user list showing all setup users with names and photos - Add tooltips for first 5 users (as preview) * Add flag to force the 'setup waka' notice --------- Co-authored-by: Max Wofford <max@maxwofford.com>
This commit is contained in:
parent
608cd67326
commit
83c2987ab3
3 changed files with 227 additions and 11 deletions
|
|
@ -103,7 +103,7 @@
|
|||
grid-template-columns: 1fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.video-container {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
|
@ -212,14 +212,20 @@
|
|||
}
|
||||
|
||||
@keyframes flash {
|
||||
0% { background-color: var(--uchu-yellow); }
|
||||
100% { background-color: transparent; }
|
||||
0% {
|
||||
background-color: var(--uchu-yellow);
|
||||
}
|
||||
100% {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
/* Setup tracking button styling */
|
||||
.setup-notice {
|
||||
text-align: left;
|
||||
margin: 2rem 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.setup-button {
|
||||
|
|
@ -265,15 +271,181 @@
|
|||
}
|
||||
}
|
||||
|
||||
.setup-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 1rem;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.setup-hint {
|
||||
margin-top: 0.5rem;
|
||||
margin: 0;
|
||||
margin-left: 0.5rem;
|
||||
font-style: italic;
|
||||
color: var(--muted-color);
|
||||
animation: bounce 2s ease infinite;
|
||||
}
|
||||
|
||||
.recent-setup-users {
|
||||
display: flex;
|
||||
margin: 0;
|
||||
margin-left: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.avatar-container {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease;
|
||||
z-index: 1;
|
||||
margin-left: -15px;
|
||||
}
|
||||
|
||||
.avatar-container:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.avatar-container:hover {
|
||||
transform: translateY(-3px);
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.setup-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid #e13950;
|
||||
object-fit: cover;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.more-avatars {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #e13950;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.all-users-hover {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.users-hover-list {
|
||||
position: absolute;
|
||||
display: none;
|
||||
left: -20px;
|
||||
top: 45px;
|
||||
background-color: #333;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
padding: 15px;
|
||||
width: 300px;
|
||||
z-index: 100;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.users-hover-list h4 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 10px;
|
||||
font-size: 16px;
|
||||
color: #eee;
|
||||
border-bottom: 1px solid #aaa;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.all-users-hover:hover .users-hover-list {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.users-hover-list:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
left: 30px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
border-bottom: 10px solid #fff;
|
||||
}
|
||||
|
||||
.hover-user-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
max-height: none;
|
||||
overflow-y: visible;
|
||||
}
|
||||
|
||||
.user-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 6px;
|
||||
border-radius: 6px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.user-item:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
margin-right: 10px;
|
||||
border: 1px solid #e13950;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.avatar-tooltip {
|
||||
position: absolute;
|
||||
top: -35px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background-color: #333;
|
||||
color: white;
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.2s, visibility 0.2s;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.avatar-tooltip:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
margin-left: -5px;
|
||||
border-width: 5px;
|
||||
border-style: solid;
|
||||
border-color: #333 transparent transparent transparent;
|
||||
}
|
||||
|
||||
.avatar-container:hover .avatar-tooltip {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 20%, 50%, 80%, 100% {
|
||||
0%,
|
||||
20%,
|
||||
50%,
|
||||
80%,
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
40% {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class StaticPagesController < ApplicationController
|
|||
redirect_to FlavorText.random_time_video.sample, allow_other_host: allowed_hosts
|
||||
end
|
||||
|
||||
@show_wakatime_setup_notice = current_user.heartbeats.empty?
|
||||
@show_wakatime_setup_notice = current_user.heartbeats.empty? || params[:show_wakatime_setup_notice]
|
||||
@setup_social_proof = get_setup_social_proof if @show_wakatime_setup_notice
|
||||
|
||||
# Get languages and editors in a single query using window functions
|
||||
|
|
@ -176,21 +176,37 @@ class StaticPagesController < ApplicationController
|
|||
|
||||
def get_setup_social_proof
|
||||
# Count users who set up in different time periods
|
||||
social_proof_for_time_period(5.minutes.ago, 1, "in the last 5 minutes") ||
|
||||
result = social_proof_for_time_period(5.minutes.ago, 1, "in the last 5 minutes") ||
|
||||
social_proof_for_time_period(1.hour.ago, 3, "in the last hour") ||
|
||||
social_proof_for_time_period(1.day.ago, 5, "today") ||
|
||||
social_proof_for_time_period(1.week.ago, 5, "in the past week") ||
|
||||
social_proof_for_time_period(1.month.ago, 5, "in the past month") ||
|
||||
social_proof_for_time_period(Time.current.beginning_of_year, 5, "this year")
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def social_proof_for_time_period(time_period, threshold, humanized_time_period)
|
||||
count_unique = Heartbeat.where("time > ?", time_period.to_f)
|
||||
.where(source_type: :test_entry)
|
||||
.distinct.count(:user_id)
|
||||
user_ids = Heartbeat.where("time > ?", time_period.to_f)
|
||||
.where(source_type: :test_entry)
|
||||
.distinct
|
||||
.pluck(:user_id)
|
||||
|
||||
|
||||
count_unique = user_ids.count
|
||||
return nil if count_unique < threshold
|
||||
|
||||
all_setup_users = User.where(id: user_ids).flat_map do |user|
|
||||
{
|
||||
id: user.id,
|
||||
avatar_url: user.avatar_url,
|
||||
display_name: user.display_name || "Hack Clubber"
|
||||
}
|
||||
end
|
||||
|
||||
@all_setup_users = all_setup_users
|
||||
@recent_setup_users = all_setup_users.take(5)
|
||||
|
||||
"#{count_unique.to_s + ' Hack Clubber'.pluralize(count_unique)} set up Hackatime #{humanized_time_period}"
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -33,7 +33,35 @@
|
|||
<% if @show_wakatime_setup_notice %>
|
||||
<div class="setup-notice">
|
||||
<%= link_to "Set up Hackatime! Click me.", my_wakatime_setup_path, class: "auth-button setup-button primary-action" %>
|
||||
<p class="setup-hint"><%= @setup_social_proof %> (this is real data)</p>
|
||||
<div class="setup-info">
|
||||
<% if @recent_setup_users&.any? %>
|
||||
<div class="recent-setup-users">
|
||||
<% @recent_setup_users.each do |user| %>
|
||||
<div class="avatar-container">
|
||||
<div class="avatar-tooltip"><%= user[:display_name] %></div>
|
||||
<img src="<%= user[:avatar_url] %>" alt="<%= user[:display_name] %>" class="setup-avatar" />
|
||||
</div>
|
||||
<% end %>
|
||||
<% if @all_setup_users && @all_setup_users.size > 5 %>
|
||||
<div class="avatar-container all-users-hover" title="See all <%= @all_setup_users.size %> users">
|
||||
<div class="setup-avatar more-avatars">+<%= @all_setup_users.size - 5 %></div>
|
||||
<div class="users-hover-list">
|
||||
<h4>All users who set up Hackatime</h4>
|
||||
<div class="hover-user-list">
|
||||
<% @all_setup_users.each do |user| %>
|
||||
<div class="user-item">
|
||||
<img src="<%= user[:avatar_url] %>" alt="<%= user[:display_name] %>" class="user-avatar" />
|
||||
<span class="user-name"><%= user[:display_name] %></span>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<p class="setup-hint"><%= @setup_social_proof %> (this is real data)</p>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue