From 209b24effa19d761bea95cfe84624ce582c3ee51 Mon Sep 17 00:00:00 2001 From: Echo Date: Mon, 26 Jan 2026 00:08:33 -0500 Subject: [PATCH] skeleton profiles (#853) --- app/controllers/profiles_controller.rb | 69 ++++++++++++------ app/views/profiles/_activity.html.erb | 8 +++ .../profiles/_activity_skeleton.html.erb | 11 +++ app/views/profiles/_editors.html.erb | 20 ++++++ app/views/profiles/_editors_skeleton.html.erb | 11 +++ app/views/profiles/_languages.html.erb | 20 ++++++ .../profiles/_languages_skeleton.html.erb | 11 +++ app/views/profiles/_projects.html.erb | 8 +++ .../profiles/_projects_skeleton.html.erb | 20 ++++++ app/views/profiles/_time_stats.html.erb | 16 +++++ .../profiles/_time_stats_skeleton.html.erb | 14 ++++ app/views/profiles/show.html.erb | 70 +++---------------- config/routes.rb | 5 ++ 13 files changed, 203 insertions(+), 80 deletions(-) create mode 100644 app/views/profiles/_activity.html.erb create mode 100644 app/views/profiles/_activity_skeleton.html.erb create mode 100644 app/views/profiles/_editors.html.erb create mode 100644 app/views/profiles/_editors_skeleton.html.erb create mode 100644 app/views/profiles/_languages.html.erb create mode 100644 app/views/profiles/_languages_skeleton.html.erb create mode 100644 app/views/profiles/_projects.html.erb create mode 100644 app/views/profiles/_projects_skeleton.html.erb create mode 100644 app/views/profiles/_time_stats.html.erb create mode 100644 app/views/profiles/_time_stats_skeleton.html.erb diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index 85888fc..c8cf7f9 100644 --- a/app/controllers/profiles_controller.rb +++ b/app/controllers/profiles_controller.rb @@ -1,7 +1,8 @@ class ProfilesController < ApplicationController - def show - @user = find(params[:username]) + before_action :find_user + before_action :check_profile_visibility, only: %i[time_stats projects languages editors activity] + def show if @user.nil? render :not_found, status: :not_found, formats: [ :html ] return @@ -9,34 +10,60 @@ class ProfilesController < ApplicationController @is_own_profile = current_user && current_user.id == @user.id @profile_visible = @user.allow_public_stats_lookup || @is_own_profile + @streak_days = @user.streak_days if @profile_visible + end - return unless @profile_visible + def time_stats + Time.use_zone(@user.timezone) do + stats = ProfileStatsService.new(@user).stats + render partial: "profiles/time_stats", locals: { + total_time_today: stats[:total_time_today], + total_time_week: stats[:total_time_week], + total_time_all: stats[:total_time_all] + } + end + end - load + def projects + Time.use_zone(@user.timezone) do + stats = ProfileStatsService.new(@user).stats + render partial: "profiles/projects", locals: { projects: stats[:top_projects_month] } + end + end + + def languages + Time.use_zone(@user.timezone) do + stats = ProfileStatsService.new(@user).stats + render partial: "profiles/languages", locals: { languages: stats[:top_languages] } + end + end + + def editors + Time.use_zone(@user.timezone) do + stats = ProfileStatsService.new(@user).stats + render partial: "profiles/editors", locals: { editors: stats[:top_editors] } + end + end + + def activity + Time.use_zone(@user.timezone) do + daily_durations = @user.heartbeats.daily_durations(user_timezone: @user.timezone).to_h + render partial: "profiles/activity", locals: { daily_durations: daily_durations, user_tz: @user.timezone } + end end private - def find(username) - User.find_by(username: username) + def find_user + @user = User.find_by(username: params[:username]) end - def load - Time.use_zone(@user.timezone) do - stats = ProfileStatsService.new(@user).stats + def check_profile_visibility + return if @user.nil? - @total_time_today = stats[:total_time_today] - @total_time_week = stats[:total_time_week] - @total_time_all = stats[:total_time_all] - @top_languages = stats[:top_languages] - @top_projects = stats[:top_projects] - @top_projects_month = stats[:top_projects_month] - @top_editors = stats[:top_editors] + is_own_profile = current_user && current_user.id == @user.id + profile_visible = @user.allow_public_stats_lookup || is_own_profile - @daily_durations = @user.heartbeats.daily_durations(user_timezone: @user.timezone).to_h - - @streak_days = @user.streak_days - @cool = @user.trust_level == 2 - end + head :not_found unless profile_visible end end diff --git a/app/views/profiles/_activity.html.erb b/app/views/profiles/_activity.html.erb new file mode 100644 index 0000000..563c7f4 --- /dev/null +++ b/app/views/profiles/_activity.html.erb @@ -0,0 +1,8 @@ +<%= turbo_frame_tag "profile_activity" do %> + <% if daily_durations.present? %> +
+

Activity

+ <%= render "static_pages/activity_graph", daily_durations: daily_durations, length_of_busiest_day: 8.hours.to_i, user_tz: user_tz %> +
+ <% end %> +<% end %> diff --git a/app/views/profiles/_activity_skeleton.html.erb b/app/views/profiles/_activity_skeleton.html.erb new file mode 100644 index 0000000..603ae13 --- /dev/null +++ b/app/views/profiles/_activity_skeleton.html.erb @@ -0,0 +1,11 @@ +
+

Activity

+
+
+ <% 364.times do %> +
+ <% end %> +
+ +
+
diff --git a/app/views/profiles/_editors.html.erb b/app/views/profiles/_editors.html.erb new file mode 100644 index 0000000..a11e49d --- /dev/null +++ b/app/views/profiles/_editors.html.erb @@ -0,0 +1,20 @@ +<%= turbo_frame_tag "profile_editors" do %> + <% if editors.present? && editors.size > 0 %> +
+

Favorite Editors

+
+ <% max_duration = editors.values.map(&:to_f).select { |d| d.finite? && d > 0 }.max || 1 %> + <% editors.each do |editor, duration| %> +
+
<%= editor %>
+
+
+ <%= ApplicationController.helpers.short_time_simple(duration) %> +
+
+
+ <% end %> +
+
+ <% end %> +<% end %> diff --git a/app/views/profiles/_editors_skeleton.html.erb b/app/views/profiles/_editors_skeleton.html.erb new file mode 100644 index 0000000..f63557c --- /dev/null +++ b/app/views/profiles/_editors_skeleton.html.erb @@ -0,0 +1,11 @@ +
+ +
+ <% 5.times do |i| %> +
+
+
+
+ <% end %> +
+
diff --git a/app/views/profiles/_languages.html.erb b/app/views/profiles/_languages.html.erb new file mode 100644 index 0000000..62472d8 --- /dev/null +++ b/app/views/profiles/_languages.html.erb @@ -0,0 +1,20 @@ +<%= turbo_frame_tag "profile_languages" do %> + <% if languages.present? && languages.size > 0 %> +
+

Top Languages

+
+ <% max_duration = languages.values.map(&:to_f).select { |d| d.finite? && d > 0 }.max || 1 %> + <% languages.each do |language, duration| %> +
+
<%= ApplicationController.helpers.display_language_name(language) || "Unknown" %>
+
+
+ <%= ApplicationController.helpers.short_time_simple(duration) %> +
+
+
+ <% end %> +
+
+ <% end %> +<% end %> diff --git a/app/views/profiles/_languages_skeleton.html.erb b/app/views/profiles/_languages_skeleton.html.erb new file mode 100644 index 0000000..f29610e --- /dev/null +++ b/app/views/profiles/_languages_skeleton.html.erb @@ -0,0 +1,11 @@ +
+ +
+ <% 5.times do |i| %> +
+
+
+
+ <% end %> +
+
diff --git a/app/views/profiles/_projects.html.erb b/app/views/profiles/_projects.html.erb new file mode 100644 index 0000000..42962ca --- /dev/null +++ b/app/views/profiles/_projects.html.erb @@ -0,0 +1,8 @@ +<%= turbo_frame_tag "profile_projects" do %> + <% if projects.present? && projects.size > 0 %> +
+

Top Projects (Past Month)

+ <%= render "profiles/project_cards", projects: projects %> +
+ <% end %> +<% end %> diff --git a/app/views/profiles/_projects_skeleton.html.erb b/app/views/profiles/_projects_skeleton.html.erb new file mode 100644 index 0000000..368636e --- /dev/null +++ b/app/views/profiles/_projects_skeleton.html.erb @@ -0,0 +1,20 @@ +
+ +
+ <% 6.times do %> +
+
+
+ +
+
+
+
+ +
+
+
+
+ <% end %> +
+
diff --git a/app/views/profiles/_time_stats.html.erb b/app/views/profiles/_time_stats.html.erb new file mode 100644 index 0000000..83706ed --- /dev/null +++ b/app/views/profiles/_time_stats.html.erb @@ -0,0 +1,16 @@ +<%= turbo_frame_tag "profile_time_stats" do %> +
+
+
Today
+
<%= ApplicationController.helpers.short_time_simple(total_time_today) %>
+
+
+
This Week
+
<%= ApplicationController.helpers.short_time_simple(total_time_week) %>
+
+
+
All Time
+
<%= ApplicationController.helpers.short_time_simple(total_time_all) %>
+
+
+<% end %> diff --git a/app/views/profiles/_time_stats_skeleton.html.erb b/app/views/profiles/_time_stats_skeleton.html.erb new file mode 100644 index 0000000..fde249b --- /dev/null +++ b/app/views/profiles/_time_stats_skeleton.html.erb @@ -0,0 +1,14 @@ +
+
+ +
+
+
+ +
+
+
+ +
+
+
diff --git a/app/views/profiles/show.html.erb b/app/views/profiles/show.html.erb index 023b7e4..379b63e 100644 --- a/app/views/profiles/show.html.erb +++ b/app/views/profiles/show.html.erb @@ -24,74 +24,26 @@ <% if @profile_visible %> + <%= turbo_frame_tag "profile_time_stats", src: profile_time_stats_path(@user.username), loading: :lazy do %> + <%= render "profiles/time_stats_skeleton" %> + <% end %> -
-
-
Today
-
<%= ApplicationController.helpers.short_time_simple(@total_time_today) %>
-
-
-
This Week
-
<%= ApplicationController.helpers.short_time_simple(@total_time_week) %>
-
-
-
All Time
-
<%= ApplicationController.helpers.short_time_simple(@total_time_all) %>
-
-
- - <% if @top_projects_month.present? && @top_projects_month.size > 0 %> -
-

Top Projects (Past Month)

- <%= render "profiles/project_cards", projects: @top_projects_month %> -
+ <%= turbo_frame_tag "profile_projects", src: profile_projects_path(@user.username), loading: :lazy do %> + <%= render "profiles/projects_skeleton" %> <% end %>
- <% if @top_languages.present? && @top_languages.size > 0 %> -
-

Top Languages

-
- <% max_duration = @top_languages.values.map(&:to_f).select { |d| d.finite? && d > 0 }.max || 1 %> - <% @top_languages.each do |language, duration| %> -
-
<%= ApplicationController.helpers.display_language_name(language) || "Unknown" %>
-
-
- <%= ApplicationController.helpers.short_time_simple(duration) %> -
-
-
- <% end %> -
-
+ <%= turbo_frame_tag "profile_languages", src: profile_languages_path(@user.username), loading: :lazy do %> + <%= render "profiles/languages_skeleton" %> <% end %> - <% if @top_editors.present? && @top_editors.size > 0 %> -
-

Favorite Editors

-
- <% max_duration = @top_editors.values.map(&:to_f).select { |d| d.finite? && d > 0 }.max || 1 %> - <% @top_editors.each do |editor, duration| %> -
-
<%= editor %>
-
-
- <%= ApplicationController.helpers.short_time_simple(duration) %> -
-
-
- <% end %> -
-
+ <%= turbo_frame_tag "profile_editors", src: profile_editors_path(@user.username), loading: :lazy do %> + <%= render "profiles/editors_skeleton" %> <% end %>
- <% if @daily_durations.present? %> -
-

Activity

- <%= render "static_pages/activity_graph", daily_durations: @daily_durations, length_of_busiest_day: 8.hours.to_i, user_tz: @user.timezone %> -
+ <%= turbo_frame_tag "profile_activity", src: profile_activity_path(@user.username), loading: :lazy do %> + <%= render "profiles/activity_skeleton" %> <% end %> <% else %>
diff --git a/config/routes.rb b/config/routes.rb index 2a4cd57..57e280b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -275,6 +275,11 @@ Rails.application.routes.draw do end get "/@:username", to: "profiles#show", as: :profile, constraints: { username: /[A-Za-z0-9_-]+/ } + get "/@:username/time_stats", to: "profiles#time_stats", as: :profile_time_stats, constraints: { username: /[A-Za-z0-9_-]+/ } + get "/@:username/projects", to: "profiles#projects", as: :profile_projects, constraints: { username: /[A-Za-z0-9_-]+/ } + get "/@:username/languages", to: "profiles#languages", as: :profile_languages, constraints: { username: /[A-Za-z0-9_-]+/ } + get "/@:username/editors", to: "profiles#editors", as: :profile_editors, constraints: { username: /[A-Za-z0-9_-]+/ } + get "/@:username/activity", to: "profiles#activity", as: :profile_activity, constraints: { username: /[A-Za-z0-9_-]+/ } # SEO routes get "/sitemap.xml", to: "sitemap#sitemap", defaults: { format: "xml" }