From b7a908833baa32ca04c386b58c5bf0492cb56da7 Mon Sep 17 00:00:00 2001 From: Echo Date: Mon, 23 Jun 2025 16:09:25 -0400 Subject: [PATCH 1/3] settings redesign --- app/assets/stylesheets/application.css | 1 - app/assets/stylesheets/settings.css | 160 ----- .../_timezone_leaderboard_toggle.html.erb | 24 +- .../users/_wakatime_config_display.html.erb | 7 +- app/views/users/edit.html.erb | 655 +++++++++--------- 5 files changed, 357 insertions(+), 490 deletions(-) delete mode 100644 app/assets/stylesheets/settings.css diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 06871fa..7471403 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -10,7 +10,6 @@ */ @import "https://uchu.style/color.css"; -@import "https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"; @import "settings.css"; /* Force dark mode for all elements */ diff --git a/app/assets/stylesheets/settings.css b/app/assets/stylesheets/settings.css deleted file mode 100644 index 387971a..0000000 --- a/app/assets/stylesheets/settings.css +++ /dev/null @@ -1,160 +0,0 @@ -.settings-page .grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(min(100%, 400px), 1fr)); - gap: 1rem; - margin-top: 2rem; -} - -.settings-page article { - background: var(--pico-card-background-color); - border: var(--pico-border-width) solid var(--pico-card-border-color); - border-radius: var(--pico-border-radius); - box-shadow: var(--pico-card-box-shadow); - transition: box-shadow var(--pico-transition); - margin: 0; -} - -.settings-page article header { - padding: 0.5rem; - border-bottom: 1px solid var(--pico-muted-border-color); -} - -.settings-page article header h2 { - margin-bottom: 0; - font-size: 1.25rem; - font-weight: 600; - color: var(--pico-color); -} - -.settings-page article header p { - margin-bottom: 0; - color: var(--pico-muted-color); - font-size: 0.875rem; -} - -.settings-page article section { - margin-top: 1.5rem; - padding-top: 1rem; - border-top: 1px solid var(--pico-muted-border-color); -} - -.settings-page article section:first-of-type { - margin-top: 0; - padding-top: 0; - border-top: none; -} - -.settings-page article section h3 { - margin-bottom: 0.75rem; - font-size: 1rem; - font-weight: 500; - color: var(--pico-color); -} - -.settings-page article .form-group { - margin-bottom: 1rem; -} - -.settings-page article .form-group:last-child { - margin-bottom: 0; -} - -.settings-page article button[role="button"], -.settings-page article input[type="submit"][role="button"] { - margin-top: 1rem; -} - -.settings-page article .secondary { - background-color: var(--pico-secondary-background); - border-color: var(--pico-secondary-border); - color: var(--pico-secondary-color); -} - -.settings-page article .secondary:hover { - background-color: var(--pico-secondary-hover-background); - border-color: var(--pico-secondary-hover-border); -} - -.settings-page article .code-example { - margin: 1rem 0; - background: var(--pico-code-background-color); - border-radius: var(--pico-border-radius); -} - -.settings-page article .code-example pre { - margin: 0; - background: transparent; - padding: 0; -} - -.settings-page article img { - max-width: 100%; - height: auto; - margin: 1rem 0; - border-radius: var(--pico-border-radius); -} - -.settings-page article pre:not(.code-example pre) { - background: var(--pico-code-background-color); - padding: 0.75rem; - border-radius: var(--pico-border-radius); - font-size: 0.875rem; - overflow-x: auto; -} - -.settings-page .mirror { - padding: 1rem; - background: var(--pico-card-sectioning-background-color); - border-radius: var(--pico-border-radius); - margin-bottom: 1rem; -} - -.settings-page .mirror:last-child { - margin-bottom: 0; -} - -.settings-page .email-form { - margin-top: 1rem; - padding-top: 1rem; - border-top: 1px solid var(--pico-muted-border-color); -} - -.settings-page .email-form .field { - display: flex; - gap: 0.5rem; - align-items: end; -} - -.settings-page .email-form input[type="email"] { - flex: 1; - margin-bottom: 0; -} - -@media (max-width: 768px) { - .settings-page .grid { - grid-template-columns: 1fr; - gap: 1.5rem; - } - - .settings-page article { - padding: 1rem; - } - - .settings-page .email-form .field { - flex-direction: column; - align-items: stretch; - } -} - -/* Dark mode styles (now default) */ -.settings-page article { - background: var(--pico-card-background-color, #1e293b); - border-color: var(--pico-card-border-color, #334155); -} - -.settings-page article:hover { - box-shadow: var( - --pico-card-box-shadow-hover, - 0 0.125rem 1rem rgba(0, 0, 0, 0.3) - ); -} diff --git a/app/views/users/_timezone_leaderboard_toggle.html.erb b/app/views/users/_timezone_leaderboard_toggle.html.erb index 898e929..a6572b9 100644 --- a/app/views/users/_timezone_leaderboard_toggle.html.erb +++ b/app/views/users/_timezone_leaderboard_toggle.html.erb @@ -1,11 +1,13 @@ -
-

- Regional & Timezone Leaderboards
- Access regional leaderboards that show users in your timezone region or specific timezone. Choose between timezone-specific, regional (UTC offset), or global competition modes. -

- <%= form_with model: user, url: (@is_own_settings ? my_settings_path : settings_user_path(user)), method: :patch, local: false do |f| %> - <%= f.check_box :default_timezone_leaderboard, checked: user.default_timezone_leaderboard, id: "user_default_timezone_leaderboard" %> - <%= f.label :default_timezone_leaderboard, "Default to Timezone Leaderboard", for: "user_default_timezone_leaderboard" %> - <%= f.submit "Save", role: "button" %> - <% end %> -
+<%= form_with model: user, url: (@is_own_settings ? my_settings_path : settings_user_path(user)), method: :patch, local: false, class: "space-y-4" do |f| %> +
+ <%= f.check_box :default_timezone_leaderboard, + checked: user.default_timezone_leaderboard, + id: "user_default_timezone_leaderboard", + class: "w-4 h-4 text-primary border-gray-600 rounded focus:ring-primary bg-gray-800" %> + <%= f.label :default_timezone_leaderboard, "Default to Timezone Leaderboard", + for: "user_default_timezone_leaderboard", + class: "text-sm text-gray-200" %> +
+

Access regional leaderboards that show users in your timezone region or specific timezone. Choose between timezone-specific, regional (UTC offset), or global competition modes.

+ <%= f.submit "Save", class: "w-full px-4 py-2 bg-primary text-white font-medium rounded-lg transition-colors duration-200" %> +<% end %> diff --git a/app/views/users/_wakatime_config_display.html.erb b/app/views/users/_wakatime_config_display.html.erb index e2f6d42..6797825 100644 --- a/app/views/users/_wakatime_config_display.html.erb +++ b/app/views/users/_wakatime_config_display.html.erb @@ -1,5 +1,5 @@ <% if @user.api_keys.any? %> -
+  
 # put this in your ~/.wakatime.cfg file
 
 [settings]
@@ -7,10 +7,9 @@ api_url = https://<%= request.host_with_port %>/api/hackatime/v1
 api_key = <%= @user.api_keys.last.token %>
 heartbeat_rate_limit_seconds = 30
 
-# any other wakatime configs you want to add: https://github.com/wakatime/wakatime-cli/blob/develop/USAGE.md#ini-config-file
-  
+# any other wakatime configs you want to add: https://github.com/wakatime/wakatime-cli/blob/develop/USAGE.md#ini-config-file
<% else %>

- No API keys found. Please migrate your keys from waka.hackclub.com below. New API key generation has yet to be implemented. +No API keys found. Please migrate your keys from waka.hackclub.com below. New API key generation has yet to be implemented.

<% end %> diff --git a/app/views/users/edit.html.erb b/app/views/users/edit.html.erb index b3a2dd4..9de3072 100644 --- a/app/views/users/edit.html.erb +++ b/app/views/users/edit.html.erb @@ -2,358 +2,385 @@ <%= @is_own_settings ? "My Settings" : "Settings | #{@user.username}" %> <% end %> -<% content_for :body_class, "settings-page" %> - -
-
-

<%= @is_own_settings ? "My Settings" : "Settings for #{@user.username}" %>

-

Change your settings for Hackatime and Sailors Log.

+
+
+

+ <%= @is_own_settings ? "My Settings" : "Settings for #{@user.username}" %> +

+

Change your Hackatime experience and preferences

-
+
+
+
+
+ ๐Ÿš€ +
+

Time Tracking Wizard

+
+

Get started with tracking your coding time in just a few minutes.

+ <%= link_to "Set up time tracking", my_wakatime_setup_path, + class: "inline-flex items-center gap-2 px-4 py-2 bg-primary text-white font-medium rounded transition-colors duration-200" %> +
-
-
-

๐Ÿš€ Time tracking wizard

-
-

Get started with tracking your coding time in just a few minutes.

- <%= link_to "Set up time tracking", my_wakatime_setup_path, role: "button" %> -
- -
-
-

๐ŸŒ Timezone

-
+
+
+
+ ๐ŸŒ +
+

Timezone

+
<%= form_with model: @user, url: @is_own_settings ? my_settings_path : settings_user_path(@user), - method: :patch do |f| %> -
- <%= f.label :timezone, "Your timezone" %> - <%= f.select :timezone, + method: :patch, local: false, + class: "space-y-4" do |f| %> +
+ <%= f.label :timezone, "Your timezone", class: "block text-sm font-medium text-gray-200 mb-2" %> + <%= f.select :timezone, TZInfo::Timezone.all.map(&:identifier).sort, - include_blank: @user.timezone.blank?, class: "form-select" %> + { include_blank: @user.timezone.blank? }, + { class: "w-full px-3 py-2 bg-gray-800 border border-gray-600 rounded text-white focus:border-primary focus:ring-1 focus:ring-primary" } %>
- This affects how your activity graph and other time-based features are displayed. - <%= f.submit "Save Settings", role: "button" %> +

This affects how your activity graph and other time-based features are displayed.

+ <%= f.submit "Save Settings", class: "w-full px-4 py-2 bg-primary text-white font-medium rounded transition-colors duration-200" %> <% end %> -
+
-
-
-

๐Ÿ’ฌ Slack Integration

-
- -
-

Status Updates

-

When you're hacking on a project, Hackatime can update your Slack status so you can show it off!

- <% unless @can_enable_slack_status %> - <%= link_to "Re-authorize with Slack to give permission to update your status", slack_auth_path, role: "button", class: "secondary" %> - <% end %> - <%= form_with model: @user, - url: @is_own_settings ? my_settings_path : settings_user_path(@user), - method: :patch do |f| %> -
- -
- <%= f.submit "Save Settings", role: "button" %> - <% end %> -
- -
-

Channel Notifications

- <% if @enabled_sailors_logs.any? %> -

You have notifications enabled for the following channels:

-
    - <% @enabled_sailors_logs.each do |sl| %> -
  • - <%= render "shared/slack_channel_mention", channel_id: sl.slack_channel_id %> -
  • - <% end %> -
- <% else %> -

You have no notifications enabled.

- <% end %> -

- You can enable notifications for specific channels by running /sailorslog on in the Slack channel you want to enable notifications for. -

-
-
- -
-
-

๐Ÿ”— Connected Accounts

-
- -
-

GitHub Account

-

This is used to show your active projects on the leaderboard & current hacking activity on the dashboard.

- <% if @user.github_uid.present? %> -

โœ… Your GitHub account is linked: <%= link_to "@#{@user.github_username}", "https://github.com/#{@user.github_username}", target: "_blank" %>

- <% if @user.github_access_token.present? %> - <%= link_to "Relink GitHub Account", github_auth_path, data: { turbo: "false" }, role: "button" %> - <%= link_to "Unlink GitHub Account", github_unlink_path, - data: { - turbo_method: :delete, - confirm: "Are you sure? This will remove access to your GitHub data." - }, - role: "button", - class: "outline" %> - <% else %> -

โš ๏ธ Your GitHub token has expired. Please relink your account.

- <%= link_to "Relink GitHub Account", github_auth_path, data: { turbo: "false" }, role: "button" %> - <% end %> - <% else %> - <%= link_to "Link GitHub Account", github_auth_path, data: { turbo: "false" }, role: "button" %> - <% end %> -
- -
-

Email Addresses

-

These are the email addresses associated with your account.

- <% if @user.email_addresses.any? %> -
    - <% @user.email_addresses.each do |email_address| %> -
  • - <%= email_address.email %> - <% if email_address.source.present? %> - - (from <%= email_address.source.humanize %>) - - <% end %> -
  • - <% end %> -
- <% else %> -

No email addresses found.

- <% end %> - -
-
- -
-
-

โš™๏ธ Extension Settings

-
+

Extension Settings

+
<%= form_with model: @user, url: @is_own_settings ? my_settings_path : settings_user_path(@user), - method: :patch do |f| %> -
- <%= f.label "Simple text" %> + method: :patch, local: false, + class: "space-y-4" do |f| %> +
+ <%= f.label :hackatime_extension_text_type, "Status bar text style", class: "block text-sm font-medium text-gray-200 mb-2" %> <%= f.select :hackatime_extension_text_type, - User.hackatime_extension_text_types.keys.map { |type| [type.humanize, type] }, - selected: @user.hackatime_extension_text_type, class: "form-select" %> + User.hackatime_extension_text_types.keys.map { |key| [key.humanize, key] }, + {}, + { class: "w-full px-3 py-2 bg-gray-800 border border-gray-600 rounded text-white focus:border-primary focus:ring-1 focus:ring-primary" } %>
- <%= f.submit "Save Settings", role: "button" %> + <%= f.submit "Save Settings", class: "w-full px-4 py-2 bg-primary text-white font-medium rounded transition-colors duration-200" %> <% end %> - +
-
-
-

๐Ÿ“Š Stats Badges

-
-
-

Show your coding stats on your GitHub profile with beautiful badges.

-

General Stats Badge

-

This badge shows your overall coding statistics.

- - <% gh_badge = GithubReadmeStats.new(current_user.id, "darcula") %> - -
<%= gh_badge.generate_badge_url %>
-
+ <%= form_with model: @user, + url: @is_own_settings ? my_settings_path : settings_user_path(@user), + method: :patch, local: false do |f| %> +
+ <%= f.check_box :uses_slack_status, + class: "w-4 h-4 text-primary border-gray-600 rounded focus:ring-primary bg-gray-800" %> + <%= f.label :uses_slack_status, "Update my Slack status automatically", + class: "text-sm text-gray-200" %> +
+ <%= f.submit "Save", class: "mt-3 px-4 py-2 bg-primary text-white font-medium rounded transition-colors duration-200" %> + <% end %> +
- <% if @projects.any? && @user.slack_uid.present? %> -
-

Project Stats Badge

-

This badge shows individual project statistics.

-

See the documentation for more customization options!

- - -
<%= @work_time_stats_url %>
-
+
+

Channel Notifications

+ <% if @enabled_sailors_logs.any? %> +

You have notifications enabled for the following channels:

+
    + <% @enabled_sailors_logs.each do |sl| %> +
  • + <%= render "shared/slack_channel_mention", channel_id: sl.slack_channel_id %> +
  • + <% end %> +
+ <% else %> +

You have no notifications enabled.

+ <% end %> +

+ You can enable notifications for specific channels by running /sailorslog on in the Slack channel. +

+
+ + + +
+
+
+ ๐Ÿ”’ +
+

Privacy Settings

+
+ <%= form_with model: @user, + url: @is_own_settings ? my_settings_path : settings_user_path(@user), + method: :patch, local: false, + class: "space-y-4" do |f| %> +
+ <%= f.check_box :allow_public_stats_lookup, + class: "w-4 h-4 text-primary border-gray-600 rounded focus:ring-primary bg-gray-800" %> + <%= f.label :allow_public_stats_lookup, "Allow public stats lookup", + class: "text-sm text-gray-200" %> +
+

When enabled, others can view your coding statistics through public APIs.

+ <%= f.submit "Save Settings", class: "w-full px-4 py-2 bg-primary text-white font-medium rounded transition-colors duration-200" %> <% end %> +
+ +
+
+
+ ๐Ÿ† +
+

Leaderboard Settings

+
+

Customize how you see the leaderboard

+ <%= render "timezone_leaderboard_toggle", user: @user %> +
+ +
+
+
+ ๐Ÿ”— +
+

Connected Accounts

+
+ +
+
+

GitHub Account

+

This is used to show your active projects on the leaderboard & current hacking activity on the dashboard.

+ <% if @user.github_uid.present? %> +
+ โœ… + Connected: <%= link_to "@#{@user.github_username}", "https://github.com/#{@user.github_username}", target: "_blank", class: "text-primary hover:text-primary/80 underline" %> +
+ <% unless @user.github_access_token.present? %> + <%= link_to "Relink GitHub Account", github_auth_path, data: { turbo: "false" }, + class: "inline-flex items-center gap-2 px-3 py-2 bg-primary text-white text-sm font-medium rounded transition-colors duration-200" %> + <% end %> + <% else %> + <%= link_to "Link GitHub Account", github_auth_path, data: { turbo: "false" }, + class: "inline-flex items-center gap-2 px-4 py-2 bg-primary text-white font-medium rounded transition-colors duration-200" %> + <% end %> +
+ +
+

Email Addresses

+

These are the email addresses associated with your account.

+ <% if @user.email_addresses.any? %> +
+ <% @user.email_addresses.each do |email| %> +
+ <%= email.email %> + + <%= email.source&.humanize || "Unknown" %> + +
+ <% end %> +
+ <% else %> +

No email addresses found.

+ <% end %> + <%= form_tag add_email_auth_path, data: { turbo: false }, class: "space-y-2" do %> + <%= email_field_tag :email, nil, + placeholder: "Add another email address", + required: true, + class: "w-full px-3 py-2 bg-gray-800 border border-gray-600 rounded text-white focus:border-primary focus:ring-1 focus:ring-primary text-sm" %> + <%= submit_tag "Add Email", class: "w-full px-3 py-2 bg-primary hover:bg-primary/80 text-white text-sm font-medium rounded transition-colors duration-200" %> + <% end %> +
+
+
+ +
+
+
+ ๐Ÿ“Š +
+

Stats Badges

+
+ +
+
+

General Stats Badge

+

Show your coding stats on your GitHub profile with beautiful badges.

+ +
+
+ + +
+ + <% gh_badge = GithubReadmeStats.new(current_user.id, "darcula") %> +
+ +
<%= gh_badge.generate_badge_url %>
+
+
+
+ + <% if @projects.any? && @user.slack_uid.present? %> +
+

Project Stats Badge

+
+ + +
+ +
<%= @work_time_stats_url %>
+
+
+
+ <% end %> +
- +
-
-
-

๐Ÿ“ Markscribe Templates

-

Use markscribe to create beautiful GitHub profile READMEs with your coding stats.

-
- -
-
{{ wakatimeDoubleCategoryBar "๐Ÿ’พ Languages:" wakatimeData.Languages "๐Ÿ’ผ Projects:" wakatimeData.Projects 5 }}
-
-

Add this to your GitHub profile README template to display your top languages and projects.

-

See the markscribe documentation for more template options.

- Example of markscribe output showing coding language and project statistics -
- -
-
-

๐Ÿ“„ Config File

-

- <% if current_user.most_recent_direct_entry_heartbeat %> - Your last heartbeat was <%= time_ago_in_words current_user.most_recent_direct_entry_heartbeat.created_at %> ago. - <% else %> - You haven't sent any heartbeats yet directly to this platform. - <% end %> +

+
+
+
+ ๐Ÿ“„ +
+

Config File

+
+

Your Wakatime configuration file for tracking coding time.

+ +
+ <%= render "wakatime_config_display" %> +
+

+ This configuration file is automatically generated and updated when you make changes to your settings.

-
+ + +
+
+
+ ๐Ÿšš +
+

Migration Assistant

+
+

This will migrate your heartbeats from waka.hackclub.com to this platform.

+ + <%= button_to "Migrate heartbeats", my_settings_migrate_heartbeats_path, method: :post, + class: "w-full px-4 py-2 bg-primary text-white font-medium rounded transition-colors duration-200" %> + + <% if @heartbeats_migration_jobs.any? %> +
+

Migration Status

+ <% @heartbeats_migration_jobs.each do |job| %> +
+ Job ID: <%= job.id %> - Status: <%= job.status %> +
+ <% end %> +
+ <% end %> +
+ + +
+
+
+ ๐Ÿ“ +
+

Markscribe Templates

+
+

Use markscribe to create beautiful GitHub profile READMEs with your coding stats.

- <%= render "wakatime_config_display" %> -

- - This file is located in ~/.wakatime.cfg on your computer. - You can configure it with other settings as well. - -

-
- -
-
-

๐Ÿ”’ Privacy Settings

-
- - <%= form_with model: @user, - url: @is_own_settings ? my_settings_path : settings_user_path(@user), - method: :patch do |f| %> -
- -
- <%= f.submit "Save Settings", role: "button" %> - <% end %> -
- -
-
-

๐Ÿ† Leaderboard settings

-

Customize how you see the leaderboard

-
- <%= render "timezone_leaderboard_toggle", user: @user %> -
- - <%# -
-
-

๐Ÿงช Beta Features

-

Enable experimental features and help us test new functionality.

-
-
- %> +
+
+
+
{{ wakatimeDoubleCategoryBar "๐Ÿ’พ Languages:" wakatimeData.Languages "๐Ÿ’ผ Projects:" wakatimeData.Projects 5 }}
+
+

Add this to your GitHub profile README template to display your top languages and projects.

+

See the markscribe documentation for more template options.

+
+
+ Example of markscribe output showing coding language and project statistics +
+
+ <% admin_tool do %> -
-
-

๐Ÿ”„ WakaTime Mirror

-

Mirror your coding activity to WakaTime.

-
- +
+
+
+ ๐Ÿ”ง +
+

WakaTime Mirrors

+
+ <% if current_user.wakatime_mirrors.any? %> -
+
<% current_user.wakatime_mirrors.each do |mirror| %> -
-

- Endpoint: <%= mirror.endpoint_url %>
- Last synced: <%= mirror.last_synced_at ? time_ago_in_words(mirror.last_synced_at) + " ago" : "Never" %> -

- <%= button_to "Delete", user_wakatime_mirror_path(current_user, mirror), method: :delete, role: "button", class: "secondary", data: { confirm: "Are you sure?" } %> +
+

<%= mirror.name %>

+

<%= mirror.endpoint_url %>

<% end %>
<% end %> - <%= form_with(model: [current_user, WakatimeMirror.new], local: true) do |f| %> -
- <%= f.label :endpoint_url, "WakaTime API Endpoint" %> - <%= f.text_field :endpoint_url, value: "https://wakatime.com/api/v1", placeholder: "https://wakatime.com/api/v1", class: "form-control" %> + <%= form_with(model: [current_user, WakatimeMirror.new], local: true, class: "space-y-4") do |f| %> +
+
+ <%= f.label :name, class: "block text-sm font-medium text-gray-200 mb-2" %> + <%= f.text_field :name, class: "w-full px-3 py-2 bg-gray-800 border border-gray-600 rounded text-white focus:border-primary focus:ring-1 focus:ring-primary" %> +
+
+ <%= f.label :endpoint_url, class: "block text-sm font-medium text-gray-200 mb-2" %> + <%= f.url_field :endpoint_url, class: "w-full px-3 py-2 bg-gray-800 border border-gray-600 rounded text-white focus:border-primary focus:ring-1 focus:ring-primary" %> +
- -
- <%= f.label :encrypted_api_key, "WakaTime API Key" %> - <%= f.text_field :encrypted_api_key, placeholder: "Enter your WakaTime API key", class: "form-control" %> -
- <%= f.submit "Add Mirror", role: "button" %> + <%= f.submit "Add Mirror", class: "px-4 py-2 bg-primary text-white font-medium rounded transition-colors duration-200" %> <% end %> -
+ <% end %> - -
-
-

๐Ÿšš Migration Assistant

-

This will migrate your heartbeats from waka.hackclub.com to this platform.

-
- - <%= button_to "Migrate heartbeats", my_settings_migrate_heartbeats_path, method: :post, role: "button" %> - - <% if @heartbeats_migration_jobs.any? %> -
-

Migration Jobs

-
    - <% @heartbeats_migration_jobs.each do |job| %> -
  • - <% if job.finished_at && !job.error %> - โœ… - <% elsif job.finished_at && job.error %> - โŒ - <% else %> - โณ - <% end %> - Job started at <%= job.created_at.strftime("%Y-%m-%d %H:%M:%S") %> - <% if job.finished_at %> - (and finished after <%= distance_of_time_in_words(job.finished_at - job.created_at) %>) - <% end %> - <% admin_tool('', 'span') do %> - <%= link_to "View job", GoodJob::Engine.routes.url_helpers.job_path(job.id) %> - <% end %> -
  • - <% end %> -
-
- <% end %> -
-
+ From c45e0fc043de8318862f8a0927e74dec4354ea2a Mon Sep 17 00:00:00 2001 From: Echo Date: Tue, 24 Jun 2025 16:15:28 -0400 Subject: [PATCH 2/3] dumb errors --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3d33814..a83dc62 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Hackatime! +# Hackatime [![Ping](https://status.hackatime.hackclub.com/api/badge/1/ping)](https://status.hackatime.hackclub.com/status/hackatime) [![Status](https://status.hackatime.hackclub.com/api/badge/1/status)](https://status.hackatime.hackclub.com/status/hackatime) @@ -48,9 +48,9 @@ app# bin/rails c # start an interactive irb! app# bin/rails db:migrate # migrate the database ``` -You can now access the app at http://localhost:3000/ +You can now access the app at -Use email authentication from the homepage with `test@example.com` or create a new user (you can view outgoing emails at http://localhost:3000/letter_opener)! +Use email authentication from the homepage with `test@example.com` or create a new user (you can view outgoing emails at [http://localhost:3000/letter_opener](http://localhost:3000/letter_opener))! Ever need to setup a new database? From e986c2debe235161f0fb3e9643c71aa0eb8927af Mon Sep 17 00:00:00 2001 From: Echo Date: Tue, 24 Jun 2025 16:23:59 -0400 Subject: [PATCH 3/3] pull out picocss and redo admin timeline --- app/assets/stylesheets/admin_timeline.css | 248 ------- app/assets/tailwind/application.css | 3 + .../controllers/trust_level_controller.js | 6 +- app/views/admin/timeline/show.html.erb | 656 ++++++++---------- 4 files changed, 294 insertions(+), 619 deletions(-) delete mode 100644 app/assets/stylesheets/admin_timeline.css diff --git a/app/assets/stylesheets/admin_timeline.css b/app/assets/stylesheets/admin_timeline.css deleted file mode 100644 index 1d5ddf0..0000000 --- a/app/assets/stylesheets/admin_timeline.css +++ /dev/null @@ -1,248 +0,0 @@ -.admin-timeline-view-wrapper { - overflow-x: auto; /* Enables horizontal scrolling */ - width: 100%; /* Takes full width of its parent in the main layout */ - -webkit-overflow-scrolling: touch; /* Smooth scrolling on iOS */ - flex-grow: 1; /* Allow it to take available vertical space in flex layouts */ - display: flex; /* Needed for flex-grow to work properly with its child */ - flex-direction: column; -} - -.admin-timeline-content-sizer { - /* min-width is set inline dynamically via ERB based on number of users and min_column_width_px */ - display: block; /* Or inline-block; block should be fine to ensure it respects min-width */ - /* This element will be wider than its parent if content requires it, triggering scroll on admin-timeline-view-wrapper */ -} - -.admin-timeline-sticky-header { - /* Changed structure to use simple absolute positioning, no more flex layout */ - position: sticky; /* Sticky to the top of admin-timeline-view-wrapper */ - top: 0; - z-index: 20; /* Above other content */ - background-color: #1F2937; /* Match page background to avoid transparency issues */ - padding-bottom: 0.5rem; - padding-top: 0.25rem; - box-sizing: border-box; - /* No padding left/right as we use absolute positioning for the headers */ -} - -/* Removed header spacer and container styles as they're no longer used */ - -.admin-timeline-user-header-cell { - position: absolute; /* Positioned absolutely within the sticky header container */ - /* left is set inline per instance - using the EXACT same calculation as activity spans */ - top: 0; /* All headers start at the top of the container */ - min-width: 186px; /* Minimum width for each header cell */ - width: 186px; /* Fixed width set inline to match span columns exactly */ - box-sizing: border-box; /* Include padding and border in width/height */ - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - text-align: center; - font-weight: 500; - color: #E5E7EB; - font-size: 0.875rem; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - padding: 0.1rem 0.25rem; /* Padding for content */ -} - -#timeline-grid-scroll-container { /* Original ID, holds hour rows and activity spans */ - overflow-y: auto; /* Retains vertical scroll for hours */ - position: relative; /* For absolute positioning of spans and current time line */ - flex-grow: 1; /* Takes remaining vertical space within admin-timeline-content-sizer */ - /* Width is implicitly 100% of admin-timeline-content-sizer, which can be very wide */ -} - -/* Span items representing user activity */ -.admin-timeline-span-item { - position: absolute; - /* background-color is set by --span-color CSS variable, applied inline */ - color: #FFFFFF; - border-radius: 0.25rem; - font-size: 0.75rem; - line-height: 1.1; - padding: 2px 4px; - box-shadow: 0 1px 3px rgba(0,0,0,0.2); - z-index: 10; /* Above grid lines */ - overflow: hidden; /* Clip content that overflows */ - box-sizing: border-box; - min-width: 186px; /* Minimum width for each span "column" */ - width: 186px; /* Fixed width set inline to match exactly with headers */ -} - -.admin-timeline-span-item a { /* Ensure links inside spans are visible and styled */ - color: inherit; /* Inherit color from parent span */ - text-decoration: underline; -} -.admin-timeline-span-item a:hover { - color: #FACC15; /* Example: yellow hover for links */ -} - -/* Styling for the hour rows that form the timeline background grid */ -.admin-timeline-hour-row { - display: flex; /* To layout hour label container and gridline container */ - align-items: center; - border-top: 1px solid #374151; /* Horizontal line for the hour */ - position: relative; /* For the absolutely positioned detailed grid line */ - box-sizing: border-box; - /* height for each hour row is set inline via ERB (pixels_per_hour) */ -} - -.admin-timeline-hour-label-container { /* Container for the hour text, e.g., "9:00 AM" */ - /* width for this container is set inline via ERB (line_left_rem) */ - flex-shrink: 0; /* Prevent shrinking */ - font-size: 0.75rem; - line-height: 1rem; - color: #6B7280; - padding-right: 0.5rem; - text-align: right; - box-sizing: border-box; - height: 100%; /* Match parent row height */ - display: flex; /* To vertically center text if needed, though align-items on parent might suffice */ - align-items: flex-start; /* Vertically center text */ -} - -.admin-timeline-hour-gridline-container { /* Container that stretches across for the faint mid-hour line */ - flex-grow: 1; /* Take remaining width */ - height: 100%; /* Fill the row height */ - position: relative; /* For the absolutely positioned line itself */ -} - -.admin-timeline-hour-gridline { /* The faint horizontal line in the middle of an hour block */ - position: absolute; - /* left: 0; right: (calculated from line_right_rem) are applied inline */ - top: 50%; - border-bottom: 1px solid #374151; /* Color of the grid line */ - transform: translateY(-50%); - z-index: 1; /* Behind activity spans */ -} - -/* Current time indicator line */ -.admin-timeline-now-marker { - position: absolute; - /* left, right, top positions are set inline via ERB */ - height: 2px; - background-color: #F87171; /* Prominent color for "now" line */ - z-index: 15; /* Above grid lines, potentially above spans if desired (adjust z-index accordingly) */ -} -.admin-timeline-now-marker-text { /* For the "NOW" text label */ - position: absolute; - left: -2.5rem; /* Adjust to position text to the left of the line_left_rem area */ - top: -0.4rem; /* Adjust to vertically center with the line */ - font-size: 0.65rem; - color: #F87171; /* Match line color */ - background-color: #1F2937; /* Match page background to make it appear to "cut through" */ - padding: 0 0.2rem; - white-space: nowrap; -} - -.user-trust-red { - background-color: rgba(239, 68, 68, 0.15) !important; - border-left: 3px solid rgb(239, 68, 68) !important; -} - -.user-trust-green { - background-color: rgba(16, 185, 129, 0.15) !important; - border-left: 3px solid rgb(16, 185, 129) !important; -} - -.user-trust-yellow { - background-color: rgba(245, 158, 11, 0.15) !important; - border-left: 3px solid rgb(245, 158, 11) !important; -} - -.user-trust-blue { - background-color: rgba(59, 130, 246, 0.1) !important; - border-left: 3px solid rgb(59, 130, 246) !important; -} - -.user-trust-indicator { - display: inline-block; - margin-left: 4px; - font-size: 0.875rem; -} - -.conviction-hammer { - cursor: pointer; - margin-left: 6px; - padding: 2px 4px; - border-radius: 4px; - transition: background-color 0.2s; -} - -.conviction-hammer:hover { - background-color: rgba(255, 255, 255, 0.1); -} - -.cm { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(0, 0, 0, 0.5); - display: flex; - align-items: center; - justify-content: center; - z-index: 1000; -} - -.cm-content { - background-color: #1F2937; - border-radius: 8px; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); - width: 90%; - max-width: 500px; -} - -.cm-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 16px; - border-bottom: 1px solid #374151; -} - -.cm-header h3 { - margin: 0; - color: #F3F4F6; - font-size: 1.25rem; -} - -.cm-close { - background: none; - border: none; - color: #9CA3AF; - font-size: 1.5rem; - cursor: pointer; -} - -.cm-body { - padding: 16px; - color: #D1D5DB; -} - -.cos { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 12px; - margin-top: 16px; -} - -.co { - background-color: #2D3748; - border: 1px solid #4B5563; - border-radius: 6px; - padding: 12px; - color: #E5E7EB; - font-size: 0.875rem; - cursor: pointer; - transition: background-color 0.2s; - text-align: center; -} - -.co:hover { - background-color: #374151; -} \ No newline at end of file diff --git a/app/assets/tailwind/application.css b/app/assets/tailwind/application.css index 471a079..e298fad 100644 --- a/app/assets/tailwind/application.css +++ b/app/assets/tailwind/application.css @@ -52,6 +52,9 @@ .bg-dark { background-color: var(--color-dark); } + .bg-darkless { + background-color: var(--color-darkless); + } .bg-sheet { background-color: var(--color-sheet); } diff --git a/app/javascript/controllers/trust_level_controller.js b/app/javascript/controllers/trust_level_controller.js index 91615c2..5231fa6 100644 --- a/app/javascript/controllers/trust_level_controller.js +++ b/app/javascript/controllers/trust_level_controller.js @@ -28,8 +28,7 @@ export default class extends Controller { method: "PATCH", headers: { "Content-Type": "application/json", - "X-CSRF-Token": document.querySelector('meta[name="csrf-token"]') - .content, + "X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').content, }, body: JSON.stringify({ trust_level: trustLevel }), }); @@ -40,10 +39,7 @@ export default class extends Controller { ); } - // Update the current trust level in the dataset event.target.dataset.currentTrustLevel = trustLevel; - - // Update the leaderboard entry's omitted class const leaderboardEntry = event.target.closest(".leaderboard-entry"); if (leaderboardEntry) { if (trustLevel === "red") { diff --git a/app/views/admin/timeline/show.html.erb b/app/views/admin/timeline/show.html.erb index 32b16e4..8de83ae 100644 --- a/app/views/admin/timeline/show.html.erb +++ b/app/views/admin/timeline/show.html.erb @@ -13,35 +13,10 @@ num_users = users_data_array.count num_users = 1 if num_users == 0 # Ensure num_users is at least 1 for calculations - # Fixed REM values for layout (assuming 1rem = 16px for px calculations) - line_left_rem = 4.0 - line_right_rem = 0.5 - activity_col_area_start_rem = line_left_rem # Header spacer aligns with hour labels - activity_col_area_end_rem = line_right_rem # Header spacer aligns with right padding of grid - - gutter_rem = 0.25 pixels_per_hour = 128 # Controls vertical scale pixels_per_minute = pixels_per_hour / 60.0 - min_column_width_px = 186 # Minimum width for each user column - gutter_px = gutter_rem * 16 # Gutter in pixels - - # Calculate the total minimum width required for all user columns + gutters + fixed label/padding areas - # This width will be applied to the admin-timeline-content-sizer div - user_columns_total_min_width_px = (num_users * min_column_width_px) - gutters_total_width_px = (num_users > 1 ? (num_users - 1) * gutter_px : 0) - - # Total min width for the content that scrolls (headers part) - min_header_content_width_px = user_columns_total_min_width_px + gutters_total_width_px - - # Total min width for the grid content part (timeline-grid-scroll-container's content) - # This area includes the hour labels on the left, then the user activity columns, then right padding - min_grid_content_width_px = (line_left_rem * 16) + min_header_content_width_px + (line_right_rem * 16) - - # The sizer div needs to be at least as wide as the widest of its direct children (header row or grid row) - # The header row's actual content part (user headers) spans min_header_content_width_px. - # Add fixed spacers for the header: - total_min_scroll_width_px = (activity_col_area_start_rem * 16) + min_header_content_width_px + (activity_col_area_end_rem * 16) + gutter_px = 4 # Current admin user and selected users for Stimulus current_admin_user = { @@ -55,189 +30,179 @@ current_date_for_form = @date.to_s %> -
- - +
-
-
- -
- - -
-
- -
-
- - -
- +
+
+ +
+ + +
-
+
<%# Selected user pills will appear here %>
- -
-
+
+
<%= @date.in_time_zone(primary_user_tz).strftime("%A, %B %-d, %Y") %>
-
- <%= link_to "โ† Prev", admin_timeline_path(date: @prev_date.to_s), class: "button button-outline", data: { "date-nav-link": "true" } %> - <%= link_to "Today", admin_timeline_path(date: Time.current.to_date.to_s), class: "button button-outline", data: { "date-nav-link": "true" } %> - <%= link_to "Next โ†’", admin_timeline_path(date: @next_date.to_s), class: "button button-outline", data: { "date-nav-link": "true" } %> +
+ <%= link_to "โ† Prev", admin_timeline_path(date: @prev_date.to_s), + class: "px-3 py-1 bg-darker rounded text-sm transition-colors", + data: { "date-nav-link": "true" } %> + <%= link_to "Today", admin_timeline_path(date: Time.current.to_date.to_s), + class: "px-3 py-1 bg-darker rounded text-sm transition-colors", + data: { "date-nav-link": "true" } %> + <%= link_to "Next โ†’", admin_timeline_path(date: @next_date.to_s), + class: "px-3 py-1 bg-darker rounded text-sm transition-colors", + data: { "date-nav-link": "true" } %>
- -
- -
+
+ <% if users_data_array.any? %> + <% + hour_label_width = 80 + grid_width = hour_label_width + (users_data_array.count * (min_column_width_px + gutter_px)) + grid_height = 120 + (24 * pixels_per_hour) + %> - - <% if users_data_array.any? %> -
-
- <% users_data_array.each_with_index do |data, index| %> - <% user = data[:user] %> - <% total_coded_time_seconds = data[:total_coded_time] %> - <% - # Calculate the base offset for this column (without the hour label padding) - # This ensures evenly spaced columns considering gutters - base_offset_px = index * min_column_width_px + (index > 0 ? index * gutter_px : 0) - - # Add the hour label padding to get the absolute left position - EXACT SAME CALCULATION AS SPANS - header_left_px = (line_left_rem * 16) + base_offset_px - %> - <% - trust_level_class = "" - if user.respond_to?(:trust_level) - case user.trust_level - when "red" - trust_level_class = "user-trust-red" - when "green" - trust_level_class = "user-trust-green" - when "yellow" - trust_level_class = "user-trust-yellow" - end - end - %> -
- <% github_url = user.respond_to?(:github_profile_url) ? user.github_profile_url : nil %> - <%= render "shared/user_mention", user: user %> -
- <% if current_user && user != current_user && user.slack_uid.present? %> - <%= link_to "Slack", "slack://user?team=T0266FRGM&id=#{user.slack_uid}", target: "_blank", style: "font-size: 0.7rem; color: #9CA3AF; text-decoration: underline;" %> - <% end %> - <% if github_url.present? %> - <%= link_to "Git", github_url, target: "_blank", style: "font-size: 0.7rem; color: #9CA3AF; text-decoration: underline;" %> - <% end %> - <% if current_user && current_user.admin? && user != current_user %> - ๐Ÿ”จ - <% end %> - <% if user.respond_to?(:trust_level) %> - - <% case user.trust_level %> - <% when "red" %> - ๐Ÿ”ด - <% when "green" %> - ๐ŸŸข - <% when "yellow" %> - ๏ฟฝ - <% when "blue" %> - ๐Ÿ”ต - <% end %> - - <% else %> - - <% end %> -
- <% if total_coded_time_seconds && total_coded_time_seconds > 0 %> -
- <%= short_time_simple(total_coded_time_seconds) %> coded -
- <% else %> -
- No time coded -
- <% end %> -
<%= user.timezone %>
-
- <% end %> -
-
- <% end %> - - -
- <%# Hour markers and lines (background grid) %> - <% (timeline_start_hour..timeline_end_hour).each do |hour| %> -
-
- <%= Time.utc(2000,1,1, hour).strftime("%-l:00 %p") %> -
-
- <%# The actual line is styled to span the container using absolute positioning from CSS %> -
+
+ <% (0..23).each do |hour| %> + <% + hour_top = 120 + (hour * pixels_per_hour) + formatted_hour = Time.utc(2000,1,1, hour).strftime("%-l:00 %p") + %> +
+
+ <%= formatted_hour %>
<% end %> - - <%# Current Time Line Indicator %> + + <% users_data_array.each_with_index do |data, index| %> + <% + user = data[:user] + total_coded_time_seconds = data[:total_coded_time] + column_left = hour_label_width + (index * (min_column_width_px + gutter_px)) + trust_level_emoji = case user.respond_to?(:trust_level) ? user.trust_level : nil + when "red" then "๐Ÿ”ด" + when "green" then "๐ŸŸข" + when "yellow" then "๐ŸŸก" + else "๐Ÿ”ต" + end + + trust_level_bg = case user.respond_to?(:trust_level) ? user.trust_level : nil + when "red" then "bg-red-500/20" + when "green" then "bg-green-500/20" + when "yellow" then "bg-yellow-500/20" + when "blue" then "bg-blue-500/20" + else "bg-gray-500/20" + end + %> +
+
+
+
+ <%= render "shared/user_mention", user: user %> +
+
+ <% if current_user && user != current_user && user.slack_uid.present? %> +
+ <%= link_to "Slack", "slack://user?team=T0266FRGM&id=#{user.slack_uid}", + target: "_blank", + class: "text-xs text-blue-400 hover:text-blue-300 underline" %> +
+ <% end %> + <% if user.respond_to?(:github_profile_url) && user.github_profile_url.present? %> +
+ <%= link_to "Git", user.github_profile_url, + target: "_blank", + class: "text-xs text-green-400 hover:text-green-300 underline" %> +
+ <% end %> +
+ <%= trust_level_emoji %> +
+ <% if current_user && current_user.admin? && user != current_user %> +
+ +
+ <% end %> +
+
+ <%= total_coded_time_seconds && total_coded_time_seconds > 0 ? "#{short_time_simple(total_coded_time_seconds)} coded" : "No time coded" %> +
+ +
+ <%= user.timezone %> +
+
+ <% end %> + <% current_time_in_zone = Time.current.in_time_zone(primary_user_tz) is_today = @date == Time.current.in_time_zone(primary_user_tz).to_date show_current_time_line = is_today && current_time_in_zone.hour >= timeline_start_hour && current_time_in_zone.hour < (timeline_end_hour + 1) if show_current_time_line - minutes_from_timeline_display_start_for_now = (current_time_in_zone.hour - timeline_start_hour) * 60 + current_time_in_zone.min - current_time_line_top_px = (minutes_from_timeline_display_start_for_now * pixels_per_minute) + minutes_from_timeline_start = (current_time_in_zone.hour - timeline_start_hour) * 60 + current_time_in_zone.min + current_time_line_top_px = 120 + (minutes_from_timeline_start * pixels_per_minute) end %> <% if show_current_time_line %> -
-
NOW
+
+
NOW
<% end %> - - <%# Logic for calculating span properties (remains mostly the same) %> <% calculate_span_properties = lambda do |span_data, span_user_tz| return nil unless span_data && span_data[:start_time] && span_data[:duration] @@ -254,7 +219,7 @@ duration_seconds_in_view = effective_end_time - effective_start_time height_px = (duration_seconds_in_view / 60.0) * pixels_per_minute return nil if height_px <= 0.5 - final_top_px = (minutes_from_view_start * pixels_per_minute).round + final_top_px = 120 + (minutes_from_view_start * pixels_per_minute).round title_parts = [] title_parts << "Languages: #{span_data[:languages].join(', ')}" if span_data[:languages]&.any? project_title_segments = (span_data[:projects_edited_details] || []).map do |proj_detail| @@ -282,37 +247,31 @@ } end %> - - <%# Activity Spans %> + <% users_data_array.each_with_index do |data, index| %> <% user = data[:user] user_spans = data[:spans] block_color = user_colors[index % user_colors.length] - - # Calculate the base offset for this column (without the hour label padding) - # This ensures evenly spaced columns considering gutters - base_offset_px = index * min_column_width_px + (index > 0 ? index * gutter_px : 0) - - # Add the hour label padding to get the absolute left position - current_span_column_left_px = (line_left_rem * 16) + base_offset_px + column_left = hour_label_width + (index * (min_column_width_px + gutter_px)) %> <% Array(user_spans).each do |span_data| %> <% props = calculate_span_properties.call(span_data, user.timezone || primary_user_tz) %> <% next unless props %> -
-
+
<% if props[:projects_to_display]&.any? %> <% props[:projects_to_display].each_with_index do |project_detail, p_idx| %> <% if project_detail[:repo_url].present? %> - <%= project_detail[:name].truncate(20) %> + <%= project_detail[:name].truncate(20) %> <% else %> <%= project_detail[:name].truncate(20) %> ๐Ÿšซ <% end %> @@ -324,16 +283,15 @@ Coding Activity <% end %>
-
+
<%= props[:display_text_line2] %>
-
+
<%= props[:display_text_line3] %>
<% end %> <% end %> - <% @timeline_commit_markers.each do |commit| %> <% user_index = users_data_array.find_index { |data| data[:user].id == commit[:user_id] } %> <% next unless user_index %> @@ -341,187 +299,153 @@ <% user_tz = user&.timezone || primary_user_tz %> <% today_start_of_day_for_user = @date.in_time_zone(user_tz).beginning_of_day %> <% view_start_datetime = today_start_of_day_for_user.advance(hours: timeline_start_hour) %> - <% base_offset_px = user_index * min_column_width_px + (user_index > 0 ? user_index * gutter_px : 0) %> - <% pill_left_px = (line_left_rem * 16) + base_offset_px + min_column_width_px - 60 %> + <% column_left = hour_label_width + (user_index * (min_column_width_px + gutter_px)) %> + <% pill_left_px = column_left + 93 %> <% commit_minutes_from_view_start = ((Time.at(commit[:timestamp]).in_time_zone(user_tz) - view_start_datetime) / 60.0).to_f %> - <% commit_top_px = (commit_minutes_from_view_start * pixels_per_minute).round %> + <% commit_top_px = 120 + (commit_minutes_from_view_start * pixels_per_minute).round %> - +<%= commit[:additions] %> + class="absolute z-20 bg-darker text-white rounded-full px-2 py-1 text-xs hover:bg-gray-800 transition-colors" + style="left: <%= pill_left_px %>px; top: <%= commit_top_px %>px; transform: translateX(-50%);"> + +<%= commit[:additions] %> / - -<%= commit[:deletions] %> + -<%= commit[:deletions] %> <% end %> -
-
-
-
+
+ <% else %> +
+
+
No users selected
+
Use the search bar above to add users to the timeline
+
+
+ <% end %> +
+ + +
+