settings redesign

This commit is contained in:
Echo
2025-06-23 16:09:25 -04:00
parent 118d161e95
commit b7a908833b
5 changed files with 357 additions and 490 deletions

View File

@@ -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 */

View File

@@ -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)
);
}

View File

@@ -1,11 +1,13 @@
<div id="timezone_leaderboard_toggle">
<p>
<strong>Regional & Timezone Leaderboards</strong><br>
<small>Access regional leaderboards that show users in your timezone region or specific timezone. Choose between timezone-specific, regional (UTC offset), or global competition modes.</small>
</p>
<%= 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 %>
</div>
<%= 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| %>
<div class="flex items-center gap-3">
<%= 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" %>
</div>
<p class="text-xs text-gray-400">Access regional leaderboards that show users in your timezone region or specific timezone. Choose between timezone-specific, regional (UTC offset), or global competition modes.</p>
<%= f.submit "Save", class: "w-full px-4 py-2 bg-primary text-white font-medium rounded-lg transition-colors duration-200" %>
<% end %>

View File

@@ -1,5 +1,5 @@
<% if @user.api_keys.any? %>
<pre>
<pre class="bg-gray-800 rounded-lg text-sm text-gray-200 whitespace-pre-wrap break-all">
# 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
</pre>
# any other wakatime configs you want to add: https://github.com/wakatime/wakatime-cli/blob/develop/USAGE.md#ini-config-file</pre>
<% else %>
<p>
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.
</p>
<% end %>

View File

@@ -2,358 +2,385 @@
<%= @is_own_settings ? "My Settings" : "Settings | #{@user.username}" %>
<% end %>
<% content_for :body_class, "settings-page" %>
<main class="container">
<header>
<h1><%= @is_own_settings ? "My Settings" : "Settings for #{@user.username}" %></h1>
<p>Change your settings for Hackatime and Sailors Log.</p>
<div class="max-w-6xl mx-auto p-6 space-y-6">
<header class="text-center mb-8">
<h1 class="text-4xl font-bold text-white mb-2">
<%= @is_own_settings ? "My Settings" : "Settings for #{@user.username}" %>
</h1>
<p class="text-muted text-lg">Change your Hackatime experience and preferences</p>
</header>
<div class="grid">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="border border-primary rounded-xl p-6 hover:bg-gray-800/50 transition-all duration-200">
<div class="flex items-center gap-3 mb-4">
<div class="p-2 bg-red-600/10 rounded">
<span class="text-2xl">🚀</span>
</div>
<h2 class="text-xl font-semibold text-white">Time Tracking Wizard</h2>
</div>
<p class="text-gray-300 mb-4">Get started with tracking your coding time in just a few minutes.</p>
<%= 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" %>
</div>
<article>
<header>
<h2>🚀 Time tracking wizard</h2>
</header>
<p>Get started with tracking your coding time in just a few minutes.</p>
<%= link_to "Set up time tracking", my_wakatime_setup_path, role: "button" %>
</article>
<article>
<header>
<h2 id="user_timezone">🌍 Timezone</h2>
</header>
<div class="border border-primary rounded-xl p-6 hover:bg-gray-800/50 transition-all duration-200">
<div class="flex items-center gap-3 mb-4">
<div class="p-2 bg-red-600/10 rounded">
<span class="text-2xl">🌍</span>
</div>
<h2 class="text-xl font-semibold text-white" id="user_timezone">Timezone</h2>
</div>
<%= form_with model: @user,
url: @is_own_settings ? my_settings_path : settings_user_path(@user),
method: :patch do |f| %>
<div class="form-group">
<%= f.label :timezone, "Your timezone" %>
method: :patch, local: false,
class: "space-y-4" do |f| %>
<div>
<%= 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" } %>
</div>
<small>This affects how your activity graph and other time-based features are displayed.</small>
<%= f.submit "Save Settings", role: "button" %>
<p class="text-xs text-gray-400">This affects how your activity graph and other time-based features are displayed.</p>
<%= f.submit "Save Settings", class: "w-full px-4 py-2 bg-primary text-white font-medium rounded transition-colors duration-200" %>
<% end %>
</article>
</div>
<article>
<header>
<h2 id="user_slack_status">💬 Slack Integration</h2>
</header>
<section>
<h3>Status Updates</h3>
<p>When you're hacking on a project, Hackatime can update your Slack status so you can show it off!</p>
<% 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| %>
<fieldset>
<label>
<%= f.check_box :uses_slack_status, id: "user_uses_slack_status" %>
<%= f.label :uses_slack_status, "Update my Slack status with my current project" %>
</label>
</fieldset>
<%= f.submit "Save Settings", role: "button" %>
<% end %>
</section>
<section>
<h3 id="user_slack_notifications">Channel Notifications</h3>
<% if @enabled_sailors_logs.any? %>
<p>You have notifications enabled for the following channels:</p>
<ul>
<% @enabled_sailors_logs.each do |sl| %>
<li>
<%= render "shared/slack_channel_mention", channel_id: sl.slack_channel_id %>
</li>
<% end %>
</ul>
<% else %>
<p>You have no notifications enabled.</p>
<% end %>
<p>
You can enable notifications for specific channels by running <code>/sailorslog on</code> in the Slack channel you want to enable notifications for.
</p>
</section>
</article>
<article>
<header>
<h2 id="user_github_account">🔗 Connected Accounts</h2>
</header>
<section>
<h3>GitHub Account</h3>
<p>This is used to show your active projects on the leaderboard &amp; current hacking activity on the dashboard.</p>
<% if @user.github_uid.present? %>
<p>✅ Your GitHub account is linked: <%= link_to "@#{@user.github_username}", "https://github.com/#{@user.github_username}", target: "_blank" %></p>
<% 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 %>
<p>⚠️ Your GitHub token has expired. Please relink your account.</p>
<%= 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 %>
</section>
<section>
<h3 id="user_email_addresses">Email Addresses</h3>
<p>These are the email addresses associated with your account.</p>
<% if @user.email_addresses.any? %>
<ul>
<% @user.email_addresses.each do |email_address| %>
<li>
<%= email_address.email %>
<% if email_address.source.present? %>
<span class="super">
(from <%= email_address.source.humanize %>)
</span>
<% end %>
</li>
<% end %>
</ul>
<% else %>
<p>No email addresses found.</p>
<% end %>
<div class="email-form">
<%= form_tag add_email_auth_path, data: { turbo: false } do %>
<div class="field">
<%= email_field_tag :email, nil, placeholder: "Add another email address", required: true %>
</div>
<%= submit_tag "Add Email", role: "button", class: "secondary" %>
<% end %>
<div class="border border-primary rounded-xl p-6 hover:bg-gray-800/50 transition-all duration-200">
<div class="flex items-center gap-3 mb-4">
<div class="p-2 bg-red-600/10 rounded">
<span class="text-2xl">⚙️</span>
</div>
</section>
</article>
<article>
<header>
<h2 id="user_hackatime_extension">⚙️ Extension Settings</h2>
</header>
<h2 class="text-xl font-semibold text-white" id="user_hackatime_extension">Extension Settings</h2>
</div>
<%= form_with model: @user,
url: @is_own_settings ? my_settings_path : settings_user_path(@user),
method: :patch do |f| %>
<div class="form-group">
<%= f.label "Simple text" %>
method: :patch, local: false,
class: "space-y-4" do |f| %>
<div>
<%= 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" } %>
</div>
<%= 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 %>
</article>
</div>
<article>
<header>
<h2 id="user_stats_badges">📊 Stats Badges</h2>
</header>
<section>
<p>Show your coding stats on your GitHub profile with beautiful badges.</p>
<h3>General Stats Badge</h3>
<p>This badge shows your overall coding statistics.</p>
<select name="theme" id="theme-select" onchange="updateBadgeTheme(this.value)" class="form-select">
<% GithubReadmeStats.themes.each do |theme| %>
<option value="<%= theme %>"><%= theme.humanize %></option>
<div class="border border-primary rounded-xl p-6 hover:bg-gray-800/50 transition-all duration-200">
<div class="flex items-center gap-3 mb-4">
<div class="p-2 bg-red-600/10 rounded">
<span class="text-2xl">💬</span>
</div>
<h2 class="text-xl font-semibold text-white" id="user_slack_status">Slack Integration</h2>
</div>
<div class="space-y-4">
<div>
<h3 class="text-lg font-medium text-white mb-2">Status Updates</h3>
<p class="text-gray-300 text-sm mb-3">When you're hacking on a project, Hackatime can update your Slack status so you can show it off!</p>
<% unless @can_enable_slack_status %>
<%= link_to "Re-authorize with Slack", slack_auth_path,
class: "inline-flex items-center gap-2 px-3 py-2 bg-gray-700 hover:bg-gray-600 text-gray-200 text-sm font-medium rounded transition-colors duration-200 mb-3" %>
<% end %>
</select>
<% gh_badge = GithubReadmeStats.new(current_user.id, "darcula") %>
<img id="badge-preview" src="<%= gh_badge.generate_badge_url %>" data-url="<%= gh_badge.generate_badge_url %>">
<pre id="badge-url"><%= gh_badge.generate_badge_url %></pre>
</section>
<%= form_with model: @user,
url: @is_own_settings ? my_settings_path : settings_user_path(@user),
method: :patch, local: false do |f| %>
<div class="flex items-center gap-3">
<%= 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" %>
</div>
<%= f.submit "Save", class: "mt-3 px-4 py-2 bg-primary text-white font-medium rounded transition-colors duration-200" %>
<% end %>
</div>
<% if @projects.any? && @user.slack_uid.present? %>
<section>
<h3>Project Stats Badge</h3>
<p>This badge shows individual project statistics.</p>
<p><small>See <a href="https://github.com/pbhak/hackatime-badge">the documentation</a> for more customization options!</small></p>
<select name="project-id" id="project-select" onchange="updateBadgeProject(this.value)" class="form-select">
<% @projects.each do |project_name| %>
<option value="<%= project_name %>"><%= project_name %></option>
<% end %>
</select>
<img id="work-time-badge-preview" src="<%= @work_time_stats_url %>" data-url="<%= @work_time_stats_url %>">
<pre id="work-time-badge-url"><%= @work_time_stats_url %></pre>
</section>
<div class="border-t border-gray-700 pt-4">
<h3 class="text-lg font-medium text-white mb-2" id="user_slack_notifications">Channel Notifications</h3>
<% if @enabled_sailors_logs.any? %>
<p class="text-gray-300 text-sm mb-2">You have notifications enabled for the following channels:</p>
<ul class="space-y-1 mb-3">
<% @enabled_sailors_logs.each do |sl| %>
<li class="text-xs text-gray-300 px-2 py-1 bg-gray-800 rounded">
<%= render "shared/slack_channel_mention", channel_id: sl.slack_channel_id %>
</li>
<% end %>
</ul>
<% else %>
<p class="text-gray-300 text-sm mb-3">You have no notifications enabled.</p>
<% end %>
<p class="text-xs text-gray-400">
You can enable notifications for specific channels by running <code class="px-1 py-0.5 bg-gray-800 rounded text-gray-200">/sailorslog on</code> in the Slack channel.
</p>
</div>
</div>
</div>
<div class="border border-primary rounded-xl p-6 hover:bg-gray-800/50 transition-all duration-200">
<div class="flex items-center gap-3 mb-4">
<div class="p-2 bg-red-600/10 rounded">
<span class="text-2xl">🔒</span>
</div>
<h2 class="text-xl font-semibold text-white" id="user_privacy">Privacy Settings</h2>
</div>
<%= 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| %>
<div class="flex items-center gap-3">
<%= 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" %>
</div>
<p class="text-xs text-gray-400">When enabled, others can view your coding statistics through public APIs.</p>
<%= f.submit "Save Settings", class: "w-full px-4 py-2 bg-primary text-white font-medium rounded transition-colors duration-200" %>
<% end %>
</div>
<div class="border border-primary rounded-xl p-6 hover:bg-gray-800/50 transition-all duration-200">
<div class="flex items-center gap-3 mb-4">
<div class="p-2 bg-red-600/10 rounded">
<span class="text-2xl">🏆</span>
</div>
<h2 class="text-xl font-semibold text-white" id="user_beta_features">Leaderboard Settings</h2>
</div>
<p class="text-gray-300 text-sm mb-4">Customize how you see the leaderboard</p>
<%= render "timezone_leaderboard_toggle", user: @user %>
</div>
<div class="border border-primary rounded-xl p-6 hover:bg-gray-800/50 transition-all duration-200 md:col-span-2">
<div class="flex items-center gap-3 mb-4">
<div class="p-2 bg-red-600/10 rounded">
<span class="text-2xl">🔗</span>
</div>
<h2 class="text-xl font-semibold text-white" id="user_github_account">Connected Accounts</h2>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="space-y-3">
<h3 class="text-lg font-medium text-white">GitHub Account</h3>
<p class="text-gray-300 text-sm">This is used to show your active projects on the leaderboard & current hacking activity on the dashboard.</p>
<% if @user.github_uid.present? %>
<div class="flex items-center gap-2 p-3 bg-gray-800 border border-gray-600 rounded">
<span class="text-green-400">✅</span>
<span class="text-gray-200 text-sm">Connected: <%= link_to "@#{@user.github_username}", "https://github.com/#{@user.github_username}", target: "_blank", class: "text-primary hover:text-primary/80 underline" %></span>
</div>
<% 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 %>
</div>
<div class="space-y-3" id="user_email_addresses">
<h3 class="text-lg font-medium text-white">Email Addresses</h3>
<p class="text-gray-300 text-sm">These are the email addresses associated with your account.</p>
<% if @user.email_addresses.any? %>
<div class="space-y-2">
<% @user.email_addresses.each do |email| %>
<div class="flex items-center gap-2 p-2 bg-gray-800 border border-gray-600 rounded">
<span class="text-gray-300 text-sm"><%= email.email %></span>
<span class="text-xs px-2 py-1 bg-gray-700 text-gray-200 rounded">
<%= email.source&.humanize || "Unknown" %>
</span>
</div>
<% end %>
</div>
<% else %>
<p class="text-gray-400 text-sm">No email addresses found.</p>
<% 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 %>
</div>
</div>
</div>
<div class="border border-primary rounded-xl p-6 hover:bg-gray-800/50 transition-all duration-200">
<div class="flex items-center gap-3 mb-4">
<div class="p-2 bg-red-600/10 rounded">
<span class="text-2xl">📊</span>
</div>
<h2 class="text-xl font-semibold text-white" id="user_stats_badges">Stats Badges</h2>
</div>
<div class="space-y-6">
<div>
<h3 class="text-lg font-medium text-white mb-2">General Stats Badge</h3>
<p class="text-gray-300 text-sm mb-4">Show your coding stats on your GitHub profile with beautiful badges.</p>
<div class="space-y-3">
<div>
<label class="block text-sm font-medium text-gray-200 mb-2">Theme</label>
<select name="theme" id="theme-select" onchange="up1(this.value)"
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">
<% GithubReadmeStats.themes.each do |theme| %>
<option value="<%= theme %>"><%= theme.humanize %></option>
<% end %>
</select>
</div>
<% gh_badge = GithubReadmeStats.new(current_user.id, "darcula") %>
<div class="p-4 bg-gray-800 border border-gray-600 rounded">
<img id="badge-preview" src="<%= gh_badge.generate_badge_url %>" data-url="<%= gh_badge.generate_badge_url %>" class="mb-3 rounded">
<pre id="badge-url" class="text-xs text-gray-300 bg-gray-900 p-2 rounded overflow-x-auto"><%= gh_badge.generate_badge_url %></pre>
</div>
</div>
</div>
<% if @projects.any? && @user.slack_uid.present? %>
<div class="border-t border-gray-700 pt-4">
<h3 class="text-lg font-medium text-white mb-2">Project Stats Badge</h3>
<div class="space-y-2">
<label class="block text-sm font-medium text-gray-200">Project</label>
<select name="project" id="project-select" onchange="up2(this.value)"
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">
<% @projects.each do |project| %>
<option value="<%= project %>"><%= project %></option>
<% end %>
</select>
<div class="mt-3 p-4 bg-gray-800 border border-gray-600 rounded">
<img id="project-badge-preview" src="<%= @work_time_stats_url %>" class="mb-3 rounded">
<pre id="project-badge-url" class="text-xs text-gray-300 bg-gray-900 p-2 rounded overflow-x-auto"><%= @work_time_stats_url %></pre>
</div>
</div>
</div>
<% end %>
</div>
<script>
function updateBadgeTheme(theme) {
const originalUrl = document.getElementById('badge-preview').dataset.url;
const [baseUrl, queryString] = originalUrl.split('?');
const params = queryString.split('&').map(param => {
const [key, value] = param.split('=');
return key === 'theme' ? `theme=${theme}` : param;
});
const newUrl = `${baseUrl}?${params.join('&')}`;
document.getElementById('badge-preview').src = newUrl;
document.getElementById('badge-url').textContent = newUrl;
function up1(theme) {
const preview = document.getElementById('badge-preview');
const url = document.getElementById('badge-url');
const baseUrl = preview.dataset.url.replace(/theme=[^&]*/, '');
const newUrl = baseUrl + (baseUrl.includes('?') ? '&' : '?') + 'theme=' + theme;
preview.src = newUrl;
url.textContent = newUrl;
}
function updateBadgeProject(project) {
const originalUrl = document.getElementById('work-time-badge-preview').dataset.url;
let splitUrl = originalUrl.split('/');
splitUrl[splitUrl.length - 1] = project;
const newUrl = splitUrl.join('/');
document.getElementById('work-time-badge-preview').src = newUrl;
document.getElementById('work-time-badge-url').textContent = newUrl;
function up2(project) {
const preview = document.getElementById('project-badge-preview');
const url = document.getElementById('project-badge-url');
const baseUrl = '<%= @work_time_stats_url.gsub(@projects.first || 'example', '') %>';
const newUrl = baseUrl + project;
preview.src = newUrl;
url.textContent = newUrl;
}
</script>
</article>
</div>
<article>
<header>
<h2 id="user_markscribe">📝 Markscribe Templates</h2>
<p>Use markscribe to create beautiful GitHub profile READMEs with your coding stats.</p>
</header>
<div class="border border-primary rounded-xl p-6 hover:bg-gray-800/50 transition-all duration-200 space-y-6">
<div>
<div class="flex items-center gap-3 mb-4">
<div class="p-2 bg-red-600/10 rounded">
<span class="text-2xl">📄</span>
</div>
<h2 class="text-xl font-semibold text-white" id="user_config_file">Config File</h2>
</div>
<p class="text-gray-300 text-sm mb-4">Your Wakatime configuration file for tracking coding time.</p>
<div class="code-example">
<pre><code>{{ wakatimeDoubleCategoryBar "💾 Languages:" wakatimeData.Languages "💼 Projects:" wakatimeData.Projects 5 }}</code></pre>
</div>
<p>Add this to your GitHub profile README template to display your top languages and projects.</p>
<p><small>See the <a href="https://github.com/taciturnaxolotl/markscribe#your-wakatime-languages-formated-as-a-bar" target="_blank">markscribe documentation</a> for more template options.</small></p>
<img src="https://cdn.fluff.pw/slackcdn/524e293aa09bc5f9115c0c29c18fb4bc.png" alt="Example of markscribe output showing coding language and project statistics" width="100%"/>
</article>
<article>
<header>
<h2 id="user_config_file">📄 Config File</h2>
<p>
<% 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 %>
<div class="bg-gray-800 border border-gray-600 rounded p-4 overflow-x-auto">
<%= render "wakatime_config_display" %>
</div>
<p class="text-xs text-gray-400 mt-2">
This configuration file is automatically generated and updated when you make changes to your settings.
</p>
</header>
</div>
<%= render "wakatime_config_display" %>
<p>
<small>
This file is located in <code>~/.wakatime.cfg</code> on your computer.
You can configure it with <a href="https://github.com/wakatime/wakatime-cli/blob/develop/USAGE.md#ini-config-file">other settings</a> as well.
</small>
</p>
</article>
<div class="border-t border-gray-700 pt-6">
<div class="flex items-center gap-3 mb-4">
<div class="p-2 bg-red-600/10 rounded">
<span class="text-2xl">🚚</span>
</div>
<h2 class="text-xl font-semibold text-white" id="user_migration_assistant">Migration Assistant</h2>
</div>
<p class="text-gray-300 text-sm mb-4">This will migrate your heartbeats from waka.hackclub.com to this platform.</p>
<article>
<header>
<h2 id="user_privacy">🔒 Privacy Settings</h2>
</header>
<%= 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" %>
<%= form_with model: @user,
url: @is_own_settings ? my_settings_path : settings_user_path(@user),
method: :patch do |f| %>
<fieldset>
<label for="user_allow_public_stats_lookup">
<%= f.check_box :allow_public_stats_lookup, id: "user_allow_public_stats_lookup" %>
<%= f.label :allow_public_stats_lookup, "Allow others to look up my public coding stats via the API" %>
</label>
</fieldset>
<%= f.submit "Save Settings", role: "button" %>
<% end %>
</article>
<% if @heartbeats_migration_jobs.any? %>
<div class="mt-4 space-y-2">
<h3 class="text-sm font-medium text-white">Migration Status</h3>
<% @heartbeats_migration_jobs.each do |job| %>
<div class="p-2 bg-gray-800 border border-gray-600 rounded text-xs text-gray-300">
Job ID: <%= job.id %> - Status: <%= job.status %>
</div>
<% end %>
</div>
<% end %>
</div>
</div>
<article>
<header>
<h2 id="user_beta_features">🏆 Leaderboard settings</h2>
<p>Customize how you see the leaderboard</p>
</header>
<%= render "timezone_leaderboard_toggle", user: @user %>
</article>
<div class="border border-primary rounded-xl p-6 hover:bg-gray-800/50 transition-all duration-200 md:col-span-2">
<div class="flex items-center gap-3 mb-4">
<div class="p-2 bg-red-600/10 rounded">
<span class="text-2xl">📝</span>
</div>
<h2 class="text-xl font-semibold text-white" id="user_markscribe">Markscribe Templates</h2>
</div>
<p class="text-gray-300 text-sm mb-4">Use markscribe to create beautiful GitHub profile READMEs with your coding stats.</p>
<%#
<article>
<header>
<h2 id="user_beta_features">🧪 Beta Features</h2>
<p>Enable experimental features and help us test new functionality.</p>
</header>
</article>
%>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div>
<div class="p-4 bg-gray-800 border border-gray-600 rounded mb-4 overflow-x-auto">
<pre class="text-sm text-gray-200 whitespace-pre-wrap break-all"><code>{{ wakatimeDoubleCategoryBar "💾 Languages:" wakatimeData.Languages "💼 Projects:" wakatimeData.Projects 5 }}</code></pre>
</div>
<p class="text-gray-300 text-sm mb-2">Add this to your GitHub profile README template to display your top languages and projects.</p>
<p class="text-xs text-gray-400">See the <a href="https://github.com/taciturnaxolotl/markscribe#your-wakatime-languages-formated-as-a-bar" target="_blank" class="text-primary hover:text-primary/80 underline">markscribe documentation</a> for more template options.</p>
</div>
<div>
<img src="https://cdn.fluff.pw/slackcdn/524e293aa09bc5f9115c0c29c18fb4bc.png"
alt="Example of markscribe output showing coding language and project statistics"
class="w-full rounded border border-gray-600"/>
</div>
</div>
</div>
<% admin_tool do %>
<article>
<header>
<h2 id="wakatime_mirror">🔄 WakaTime Mirror</h2>
<p>Mirror your coding activity to WakaTime.</p>
</header>
<div class="p-6 md:col-span-2">
<div class="flex items-center gap-3 mb-4">
<div class="p-2 bg-red-600/10 rounded">
<span class="text-2xl">🔧</span>
</div>
<h2 class="text-xl font-semibold text-white">WakaTime Mirrors</h2>
</div>
<% if current_user.wakatime_mirrors.any? %>
<div class="mirrors-list">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<% current_user.wakatime_mirrors.each do |mirror| %>
<div class="mirror">
<p>
<strong>Endpoint:</strong> <%= mirror.endpoint_url %><br>
<strong>Last synced:</strong> <%= mirror.last_synced_at ? time_ago_in_words(mirror.last_synced_at) + " ago" : "Never" %>
</p>
<%= button_to "Delete", user_wakatime_mirror_path(current_user, mirror), method: :delete, role: "button", class: "secondary", data: { confirm: "Are you sure?" } %>
<div class="p-4 bg-gray-800 border border-gray-600 rounded">
<h3 class="text-white font-medium"><%= mirror.name %></h3>
<p class="text-gray-400 text-sm"><%= mirror.endpoint_url %></p>
</div>
<% end %>
</div>
<% end %>
<%= form_with(model: [current_user, WakatimeMirror.new], local: true) do |f| %>
<div class="form-group">
<%= 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| %>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<%= 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" %>
</div>
<div>
<%= 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" %>
</div>
</div>
<div class="form-group">
<%= f.label :encrypted_api_key, "WakaTime API Key" %>
<%= f.text_field :encrypted_api_key, placeholder: "Enter your WakaTime API key", class: "form-control" %>
</div>
<%= 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 %>
</article>
</div>
<% end %>
<article>
<header>
<h2 id="user_migration_assistant">🚚 Migration Assistant</h2>
<p>This will migrate your heartbeats from waka.hackclub.com to this platform.</p>
</header>
<%= button_to "Migrate heartbeats", my_settings_migrate_heartbeats_path, method: :post, role: "button" %>
<% if @heartbeats_migration_jobs.any? %>
<section>
<h3>Migration Jobs</h3>
<ul>
<% @heartbeats_migration_jobs.each do |job| %>
<li>
<% 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 %>
</li>
<% end %>
</ul>
</section>
<% end %>
</article>
</div>
</main>
</div>