From 8d00418059499567df582ea55bb0470201ad813f Mon Sep 17 00:00:00 2001 From: Mahad Kalam <55807755+skyfallwastaken@users.noreply.github.com> Date: Sun, 15 Mar 2026 15:26:32 +0000 Subject: [PATCH] Spring cleaning (#1074) * fix: use owner/repo format for project badges Updates badge URLs to use GitHub-style owner/repo format (e.g., "hackclub/hackatime") instead of project names. This ensures compatibility with external badge services that expect repository paths. Changes: - Add Repository#full_path method to get owner/repo format - Update settings controller to pass both display names and repo paths - Update Badges component to display project names but use repo paths in URLs * fix: improve user lookup in API v1 stats endpoint Use the robust lookup_user method for username parameter in the /api/v1/stats endpoint to ensure consistent user lookup across all API endpoints. This properly handles Slack UIDs (HCA IDs), numeric user IDs, and usernames in the correct priority order. * fix: reduce clutter on new user homepage Simplify the new user experience by: - Removing redundant "Hello friend" text from setup notice (header already provides context) - Hiding GitHub link banner when setup notice is shown to focus user on primary action This reduces visual clutter and helps new users focus on completing setup first. * fix: enable full app layout for new OAuth application page Remove layout=false directive that was preventing the app header and navigation from appearing on the new OAuth application creation page. * fix: add antigravity editor to docs Add documentation for Antigravity, a VSCode fork from Google with built-in AI features. Includes setup instructions for tracking time with Hackatime using the WakaTime extension. * fix: improve stat card subtitle positioning Remove absolute positioning from subtitle text to allow it to flow naturally after the main value. This prevents the subtitle from being pushed to the bottom when other cards have longer content. * fix: align settings action buttons to card end on larger screens Remove width constraint from footer to allow action buttons to align to the right edge of the full card width instead of being constrained to a narrower container. * fix: improve heartbeat importer visibility on light themes Update import provider cards and radio buttons to have better contrast on light themes: - Use bg-surface-100 instead of bg-darker for better card visibility - Increase radio button border thickness and use darker border color - Add hover and focus states for better interactivity * Split up settings controller + perf + goal display * Make stat card subtitles larger * Fix AG + VS Code * Remove Shiba refs * Bundle update --- .claude/settings.json | 8 + CLAUDE.md | 1 + Gemfile.lock | 22 +- .../api/hackatime/v1/hackatime_controller.rb | 29 +- app/controllers/api/v1/stats_controller.rb | 2 +- app/controllers/docs_controller.rb | 4 +- app/controllers/settings/access_controller.rb | 18 + app/controllers/settings/badges_controller.rb | 9 + app/controllers/settings/base_controller.rb | 299 ++++++-------- app/controllers/settings/data_controller.rb | 28 ++ app/controllers/settings/goals_controller.rb | 17 +- .../settings/integrations_controller.rb | 37 ++ .../settings/notifications_controller.rb | 7 + .../settings/profile_controller.rb | 10 + app/javascript/pages/Home/SignedIn.svelte | 4 +- .../pages/Home/signedIn/SetupNotice.svelte | 4 - .../pages/Home/signedIn/StatCard.svelte | 6 +- .../pages/OAuthApplications/New.svelte | 4 - .../pages/Users/Settings/Badges.svelte | 16 +- .../pages/Users/Settings/Data.svelte | 386 +++++++++--------- .../Settings/components/SectionCard.svelte | 6 +- app/javascript/pages/Users/Settings/types.ts | 4 +- app/models/repository.rb | 5 + docs/editors/antigravity.md | 12 + docs/editors/godot.md | 16 +- docs/editors/vs-code.md | 11 +- .../images/editor-icons/antigravity-128.png | Bin 0 -> 20190 bytes .../api/hackatime/v1/compatibility_spec.rb | 12 +- swagger/v1/swagger.yaml | 18 +- 29 files changed, 574 insertions(+), 421 deletions(-) create mode 100644 .claude/settings.json create mode 100644 CLAUDE.md create mode 100644 docs/editors/antigravity.md create mode 100644 public/images/editor-icons/antigravity-128.png diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..d0bc87b --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,8 @@ +{ + "includeCoAuthoredBy": false, + "gitAttribution": false, + "attribution": { + "commits": false, + "pullRequests": false + } +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..8d770e2 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +Read @AGENTS.md diff --git a/Gemfile.lock b/Gemfile.lock index d314c66..669bba5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -82,7 +82,7 @@ GEM ast (2.4.3) autotuner (1.1.0) aws-eventstream (1.4.0) - aws-partitions (1.1225.0) + aws-partitions (1.1226.0) aws-sdk-core (3.243.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) @@ -337,7 +337,7 @@ GEM net-ssh (>= 5.0.0, < 8.0.0) net-smtp (0.5.1) net-protocol - net-ssh (7.3.0) + net-ssh (7.3.1) nio4r (2.7.5) nokogiri (1.19.1-aarch64-linux-gnu) racc (~> 1.4) @@ -357,7 +357,7 @@ GEM faraday (>= 1.0, < 3.0) faraday-net_http_persistent net-http-persistent - oj (3.16.15) + oj (3.16.16) bigdecimal (>= 3.0) ostruct (>= 0.2) ostruct (0.6.3) @@ -548,8 +548,8 @@ GEM bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) logger - skylight (7.0.0) - activesupport (>= 7.1.0) + skylight (7.1.0) + activesupport (>= 7.2.0) slack-ruby-client (3.1.0) faraday (>= 2.0.1) faraday-mashify @@ -588,12 +588,12 @@ GEM tailwindcss-rails (4.4.0) railties (>= 7.0.0) tailwindcss-ruby (~> 4.0) - tailwindcss-ruby (4.2.0) - tailwindcss-ruby (4.2.0-aarch64-linux-gnu) - tailwindcss-ruby (4.2.0-aarch64-linux-musl) - tailwindcss-ruby (4.2.0-arm64-darwin) - tailwindcss-ruby (4.2.0-x86_64-linux-gnu) - tailwindcss-ruby (4.2.0-x86_64-linux-musl) + tailwindcss-ruby (4.2.1) + tailwindcss-ruby (4.2.1-aarch64-linux-gnu) + tailwindcss-ruby (4.2.1-aarch64-linux-musl) + tailwindcss-ruby (4.2.1-arm64-darwin) + tailwindcss-ruby (4.2.1-x86_64-linux-gnu) + tailwindcss-ruby (4.2.1-x86_64-linux-musl) thor (1.5.0) thruster (0.1.19) thruster (0.1.19-aarch64-linux) diff --git a/app/controllers/api/hackatime/v1/hackatime_controller.rb b/app/controllers/api/hackatime/v1/hackatime_controller.rb index e138f55..6070219 100644 --- a/app/controllers/api/hackatime/v1/hackatime_controller.rb +++ b/app/controllers/api/hackatime/v1/hackatime_controller.rb @@ -46,14 +46,39 @@ class Api::Hackatime::V1::HackatimeController < ApplicationController def status_bar_today Time.use_zone(@user.timezone) do hbt = @user.heartbeats.today + total_seconds = hbt.duration_seconds + + # Check if user has a daily goal + daily_goal = @user.goals.find_by(period: "day") + result = { data: { grand_total: { - text: @user.format_extension_text(hbt.duration_seconds), - total_seconds: hbt.duration_seconds + text: @user.format_extension_text(total_seconds), + total_seconds: total_seconds } } } + + # Include goal information if daily goal exists + if daily_goal + goal_progress = ProgrammingGoalsProgressService.new(user: @user, goals: [ daily_goal ]).call.first + + if goal_progress + # Append goal progress to the user's preferred text format + user_text = result[:data][:grand_total][:text] + goal_text = ApplicationController.helpers.short_time_simple(daily_goal.target_seconds) + + result[:data][:grand_total][:text] = "#{user_text} / #{goal_text} today" + result[:data][:goal] = { + target_seconds: daily_goal.target_seconds, + tracked_seconds: goal_progress[:tracked_seconds], + completion_percent: goal_progress[:completion_percent], + complete: goal_progress[:complete] + } + end + end + render json: result end end diff --git a/app/controllers/api/v1/stats_controller.rb b/app/controllers/api/v1/stats_controller.rb index 5d870b3..531216b 100644 --- a/app/controllers/api/v1/stats_controller.rb +++ b/app/controllers/api/v1/stats_controller.rb @@ -12,7 +12,7 @@ class Api::V1::StatsController < ApplicationController query = Heartbeat.where(time: start_date..end_date) if params[:username].present? - user = User.find_by(username: params[:username]) || User.find_by(slack_uid: params[:username]) + user = lookup_user(params[:username]) return render json: { error: "User not found" }, status: :not_found unless user query = query.where(user_id: user.id) diff --git a/app/controllers/docs_controller.rb b/app/controllers/docs_controller.rb index 36399d8..3ade01b 100644 --- a/app/controllers/docs_controller.rb +++ b/app/controllers/docs_controller.rb @@ -11,8 +11,8 @@ class DocsController < InertiaController ].freeze ALL_EDITORS = [ - [ "Android Studio", "android-studio" ], [ "AppCode", "appcode" ], [ "Aptana", "aptana" ], - [ "Arduino IDE", "arduino-ide" ], [ "Azure Data Studio", "azure-data-studio" ], + [ "Android Studio", "android-studio" ], [ "Antigravity", "antigravity" ], [ "AppCode", "appcode" ], + [ "Aptana", "aptana" ], [ "Arduino IDE", "arduino-ide" ], [ "Azure Data Studio", "azure-data-studio" ], [ "Brackets", "brackets" ], [ "C++ Builder", "c++-builder" ], [ "CLion", "clion" ], [ "Cloud9", "cloud9" ], [ "Coda", "coda" ], diff --git a/app/controllers/settings/access_controller.rb b/app/controllers/settings/access_controller.rb index c1d4945..22c6ab4 100644 --- a/app/controllers/settings/access_controller.rb +++ b/app/controllers/settings/access_controller.rb @@ -37,6 +37,24 @@ class Settings::AccessController < Settings::BaseController ) end + def section_props + api_key_token = @user.api_keys.last&.token + + { + settings_update_path: my_settings_access_path, + user: user_props, + options: options_props, + paths: paths_props, + config_file: { + content: generated_wakatime_config(api_key_token), + has_api_key: api_key_token.present?, + empty_message: "No API key is available yet. Rotate your API key to generate one.", + api_key: api_key_token, + api_url: "https://#{request.host_with_port}/api/hackatime/v1" + } + } + end + def access_params params.require(:user).permit(:hackatime_extension_text_type) end diff --git a/app/controllers/settings/badges_controller.rb b/app/controllers/settings/badges_controller.rb index 74e5c2f..4dca419 100644 --- a/app/controllers/settings/badges_controller.rb +++ b/app/controllers/settings/badges_controller.rb @@ -5,4 +5,13 @@ class Settings::BadgesController < Settings::BaseController settings_update_path: my_settings_profile_path ) end + + private + + def section_props + { + options: options_props, + badges: badges_props + } + end end diff --git a/app/controllers/settings/base_controller.rb b/app/controllers/settings/base_controller.rb index b66211e..0348b35 100644 --- a/app/controllers/settings/base_controller.rb +++ b/app/controllers/settings/base_controller.rb @@ -6,15 +6,13 @@ class Settings::BaseController < InertiaController before_action :set_user before_action :require_current_user - before_action :prepare_settings_page private - def render_settings_page(active_section:, settings_update_path:, status: :ok) - render inertia: settings_component_for(active_section), props: settings_page_props( - active_section: active_section, - settings_update_path: settings_update_path - ), status: status + def render_settings_page(active_section:, settings_update_path:, status: :ok, extra_props: {}) + render inertia: settings_component_for(active_section), props: common_props( + active_section: active_section + ).merge(section_props).merge(extra_props), status: status end def settings_component_for(active_section) @@ -29,45 +27,9 @@ class Settings::BaseController < InertiaController }.fetch(active_section.to_s, "Users/Settings/Profile") end - def prepare_settings_page - @is_own_settings = is_own_settings? - @can_enable_slack_status = @user.slack_access_token.present? && @user.slack_scopes.include?("users.profile:write") - @imports_enabled = Flipper.enabled?(:imports, @user) - @enabled_sailors_logs = SailorsLogNotificationPreference.where( - slack_uid: @user.slack_uid, - enabled: true, - ).where.not(slack_channel_id: SailorsLog::DEFAULT_CHANNELS) - - @projects = @user.project_repo_mappings.distinct.pluck(:project_name) - heartbeat_language_and_projects = @user.heartbeats.distinct.pluck(:language, :project) - goal_languages = [] - goal_projects = @projects.dup - - heartbeat_language_and_projects.each do |language, project| - categorized_language = language&.categorize_language - goal_languages << categorized_language if categorized_language.present? - goal_projects << project if project.present? - end - - @goal_selectable_languages = goal_languages.uniq.sort - @goal_selectable_projects = goal_projects.uniq.sort - @work_time_stats_base_url = @user.slack_uid.present? ? "https://hackatime-badge.hackclub.com/#{@user.slack_uid}/" : nil - @work_time_stats_url = if @work_time_stats_base_url.present? - "#{@work_time_stats_base_url}#{@projects.first || 'example'}" - end - - @general_badge_url = GithubReadmeStats.new(@user.id, "darcula").generate_badge_url - @latest_api_key_token = @user.api_keys.last&.token - @latest_heartbeat_import = @user.heartbeat_import_runs.latest_first.first - end - - def settings_page_props(active_section:, settings_update_path:) - if active_section.to_s == "data" && @latest_heartbeat_import.present? - @latest_heartbeat_import = HeartbeatImportRunner.refresh_remote_run!(@latest_heartbeat_import) - end - - heartbeats_last_7_days = @user.heartbeats.where("time >= ?", 7.days.ago.to_f).count - channel_ids = @enabled_sailors_logs.pluck(:slack_channel_id) + # Lightweight props shared by every settings page + def common_props(active_section:) + is_own = is_own_settings? { active_section: active_section, @@ -80,137 +42,10 @@ class Settings::BaseController < InertiaController badges: my_settings_badges_path, data: my_settings_data_path }, - page_title: (@is_own_settings ? "My Settings" : "Settings | #{@user.display_name}"), - heading: (@is_own_settings ? "Settings" : "Settings for #{@user.display_name}"), + page_title: (is_own ? "My Settings" : "Settings | #{@user.display_name}"), + heading: (is_own ? "Settings" : "Settings for #{@user.display_name}"), subheading: "Manage your profile, integrations, notifications, access, goals, and data tools.", - settings_update_path: settings_update_path, - create_goal_path: my_settings_goals_create_path, - username_max_length: User::USERNAME_MAX_LENGTH, - user: { - id: @user.id, - display_name: @user.display_name, - timezone: @user.timezone, - country_code: @user.country_code, - username: @user.username, - theme: @user.theme, - uses_slack_status: @user.uses_slack_status, - weekly_summary_email_enabled: @user.subscribed?("weekly_summary"), - hackatime_extension_text_type: @user.hackatime_extension_text_type, - allow_public_stats_lookup: @user.allow_public_stats_lookup, - trust_level: @user.trust_level, - can_request_deletion: @user.can_request_deletion?, - github_uid: @user.github_uid, - github_username: @user.github_username, - slack_uid: @user.slack_uid, - programming_goals: @user.goals.order(:created_at).map { |goal| - goal.as_programming_goal_payload.merge( - update_path: my_settings_goal_update_path(goal), - destroy_path: my_settings_goal_destroy_path(goal) - ) - } - }, - paths: { - settings_path: settings_update_path, - wakatime_setup_path: my_wakatime_setup_path, - slack_auth_path: slack_auth_path, - github_auth_path: github_auth_path, - github_unlink_path: github_unlink_path, - add_email_path: add_email_auth_path, - unlink_email_path: unlink_email_auth_path, - rotate_api_key_path: my_settings_rotate_api_key_path, - export_all_heartbeats_path: export_my_heartbeats_path(all_data: "true"), - export_range_heartbeats_path: export_my_heartbeats_path, - create_heartbeat_import_path: my_heartbeat_imports_path, - create_deletion_path: create_deletion_path - }, - options: { - countries: ISO3166::Country.all.map { |country| - { - label: country.common_name, - value: country.alpha2 - } - }.sort_by { |country| country[:label] }, - timezones: TZInfo::Timezone.all_identifiers.sort.map { |timezone| - { label: timezone, value: timezone } - }, - extension_text_types: User.hackatime_extension_text_types.keys.map { |key| - { - label: key.humanize, - value: key - } - }, - themes: User.theme_options, - badge_themes: GithubReadmeStats.themes, - goals: { - periods: Goal::PERIODS.map { |period| - { - label: period.humanize, - value: period - } - }, - preset_target_seconds: Goal::PRESET_TARGET_SECONDS, - selectable_languages: @goal_selectable_languages - .map { |language| { label: language, value: language } }, - selectable_projects: @goal_selectable_projects - .map { |project| { label: project, value: project } } - } - }, - slack: { - can_enable_status: @can_enable_slack_status, - notification_channels: channel_ids.map { |channel_id| - { - id: channel_id, - label: "##{channel_id}", - url: "https://hackclub.slack.com/archives/#{channel_id}" - } - } - }, - github: { - connected: @user.github_uid.present?, - username: @user.github_username, - profile_url: (@user.github_username.present? ? "https://github.com/#{@user.github_username}" : nil) - }, - emails: @user.email_addresses.map { |email| - { - email: email.email, - source: email.source&.humanize || "Unknown", - can_unlink: @user.can_delete_email_address?(email) - } - }, - badges: { - general_badge_url: @general_badge_url, - project_badge_url: @work_time_stats_url, - project_badge_base_url: @work_time_stats_base_url, - projects: @projects, - profile_url: (@user.username.present? ? "https://hackati.me/#{@user.username}" : nil), - markscribe_template: '{{ wakatimeDoubleCategoryBar "Languages:" wakatimeData.Languages "Projects:" wakatimeData.Projects 5 }}', - markscribe_reference_url: "https://github.com/taciturnaxolotl/markscribe#your-wakatime-languages-formated-as-a-bar", - markscribe_preview_image_url: "https://cdn.fluff.pw/slackcdn/524e293aa09bc5f9115c0c29c18fb4bc.png", - heatmap_badge_url: "https://heatmap.shymike.dev/?id=#{@user.id}&timezone=#{@user.timezone}", - heatmap_config_url: "https://hackatime-heatmap.shymike.dev/?id=#{@user.id}&timezone=#{@user.timezone}", - hackabox_repo_url: "https://github.com/quackclub/hacka-box", - hackabox_preview_image_url: "https://user-cdn.hackclub-assets.com/019cef81-366a-7543-ad7c-21b738310f23/image.png" - }, - config_file: { - content: generated_wakatime_config(@latest_api_key_token), - has_api_key: @latest_api_key_token.present?, - empty_message: "No API key is available yet. Rotate your API key to generate one.", - api_key: @latest_api_key_token, - api_url: "https://#{request.host_with_port}/api/hackatime/v1" - }, - data_export: { - total_heartbeats: number_with_delimiter(@user.heartbeats.count), - total_coding_time: @user.heartbeats.duration_simple, - heartbeats_last_7_days: number_with_delimiter(heartbeats_last_7_days), - is_restricted: (@user.trust_level == "red") - }, - imports_enabled: @imports_enabled, - remote_import_cooldown_until: HeartbeatImportRunner.remote_import_cooldown_until(user: @user)&.iso8601, - latest_heartbeat_import: HeartbeatImportRunner.serialize(@latest_heartbeat_import), - ui: { - show_dev_import: Rails.env.development?, - show_imports: @imports_enabled - }, + errors: { full_messages: @user.errors.full_messages, username: @user.errors[:username] @@ -218,6 +53,120 @@ class Settings::BaseController < InertiaController } end + # Subclasses override this to provide section-specific props + def section_props + {} + end + + # Shared helpers used by multiple section controllers + + def user_props + { + id: @user.id, + display_name: @user.display_name, + timezone: @user.timezone, + country_code: @user.country_code, + username: @user.username, + theme: @user.theme, + uses_slack_status: @user.uses_slack_status, + weekly_summary_email_enabled: @user.subscribed?("weekly_summary"), + hackatime_extension_text_type: @user.hackatime_extension_text_type, + allow_public_stats_lookup: @user.allow_public_stats_lookup, + trust_level: @user.trust_level, + can_request_deletion: @user.can_request_deletion?, + github_uid: @user.github_uid, + github_username: @user.github_username, + slack_uid: @user.slack_uid, + programming_goals: @user.goals.order(:created_at).map { |goal| + goal.as_programming_goal_payload.merge( + update_path: my_settings_goal_update_path(goal), + destroy_path: my_settings_goal_destroy_path(goal) + ) + } + } + end + + def paths_props + { + settings_path: my_settings_profile_path, + wakatime_setup_path: my_wakatime_setup_path, + slack_auth_path: slack_auth_path, + github_auth_path: github_auth_path, + github_unlink_path: github_unlink_path, + add_email_path: add_email_auth_path, + unlink_email_path: unlink_email_auth_path, + rotate_api_key_path: my_settings_rotate_api_key_path, + export_all_heartbeats_path: export_my_heartbeats_path(all_data: "true"), + export_range_heartbeats_path: export_my_heartbeats_path, + create_heartbeat_import_path: my_heartbeat_imports_path, + create_deletion_path: create_deletion_path + } + end + + def project_list + @project_list ||= @user.project_repo_mappings.includes(:repository).distinct.map do |mapping| + { display_name: mapping.project_name, repo_path: mapping.repository&.full_path || mapping.project_name } + end + end + + def options_props + heartbeat_language_and_projects = @user.heartbeats.distinct.pluck(:language, :project) + goal_languages = [] + goal_projects = project_list.map { |p| p[:display_name] } + + heartbeat_language_and_projects.each do |language, project| + categorized_language = language&.categorize_language + goal_languages << categorized_language if categorized_language.present? + goal_projects << project if project.present? + end + + { + countries: ISO3166::Country.all.map { |country| + { label: country.common_name, value: country.alpha2 } + }.sort_by { |country| country[:label] }, + timezones: TZInfo::Timezone.all_identifiers.sort.map { |timezone| + { label: timezone, value: timezone } + }, + extension_text_types: User.hackatime_extension_text_types.keys.map { |key| + { label: key.humanize, value: key } + }, + themes: User.theme_options, + badge_themes: GithubReadmeStats.themes, + goals: { + periods: Goal::PERIODS.map { |period| + { label: period.humanize, value: period } + }, + preset_target_seconds: Goal::PRESET_TARGET_SECONDS, + selectable_languages: goal_languages.uniq.sort + .map { |language| { label: language, value: language } }, + selectable_projects: goal_projects.uniq.sort + .map { |project| { label: project, value: project } } + } + } + end + + def badges_props + work_time_stats_base_url = @user.slack_uid.present? ? "https://hackatime-badge.hackclub.com/#{@user.slack_uid}/" : nil + work_time_stats_url = if work_time_stats_base_url.present? && project_list.first.present? + "#{work_time_stats_base_url}#{project_list.first[:repo_path]}" + end + + { + general_badge_url: GithubReadmeStats.new(@user.id, "darcula").generate_badge_url, + project_badge_url: work_time_stats_url, + project_badge_base_url: work_time_stats_base_url, + projects: project_list, + profile_url: (@user.username.present? ? "https://hackati.me/#{@user.username}" : nil), + markscribe_template: '{{ wakatimeDoubleCategoryBar "Languages:" wakatimeData.Languages "Projects:" wakatimeData.Projects 5 }}', + markscribe_reference_url: "https://github.com/taciturnaxolotl/markscribe#your-wakatime-languages-formated-as-a-bar", + markscribe_preview_image_url: "https://cdn.fluff.pw/slackcdn/524e293aa09bc5f9115c0c29c18fb4bc.png", + heatmap_badge_url: "https://heatmap.shymike.dev/?id=#{@user.id}&timezone=#{@user.timezone}", + heatmap_config_url: "https://hackatime-heatmap.shymike.dev/?id=#{@user.id}&timezone=#{@user.timezone}", + hackabox_repo_url: "https://github.com/quackclub/hacka-box", + hackabox_preview_image_url: "https://user-cdn.hackclub-assets.com/019cef81-366a-7543-ad7c-21b738310f23/image.png" + } + end + def generated_wakatime_config(api_key) return nil if api_key.blank? diff --git a/app/controllers/settings/data_controller.rb b/app/controllers/settings/data_controller.rb index b5ded78..0fe5909 100644 --- a/app/controllers/settings/data_controller.rb +++ b/app/controllers/settings/data_controller.rb @@ -12,4 +12,32 @@ class Settings::DataController < Settings::BaseController status: status ) end + + def section_props + imports_enabled = Flipper.enabled?(:imports, @user) + latest_import = @user.heartbeat_import_runs.latest_first.first + if latest_import.present? + latest_import = HeartbeatImportRunner.refresh_remote_run!(latest_import) + end + + { + user: user_props, + paths: paths_props, + data_export: InertiaRails.defer { + { + total_heartbeats: number_with_delimiter(@user.heartbeats.count), + total_coding_time: @user.heartbeats.duration_simple, + heartbeats_last_7_days: number_with_delimiter(@user.heartbeats.where("time >= ?", 7.days.ago.to_f).count), + is_restricted: (@user.trust_level == "red") + } + }, + imports_enabled: imports_enabled, + remote_import_cooldown_until: HeartbeatImportRunner.remote_import_cooldown_until(user: @user)&.iso8601, + latest_heartbeat_import: HeartbeatImportRunner.serialize(latest_import), + ui: { + show_dev_import: Rails.env.development?, + show_imports: imports_enabled + } + } + end end diff --git a/app/controllers/settings/goals_controller.rb b/app/controllers/settings/goals_controller.rb index 3850fcd..c79df87 100644 --- a/app/controllers/settings/goals_controller.rb +++ b/app/controllers/settings/goals_controller.rb @@ -40,10 +40,21 @@ class Settings::GoalsController < Settings::BaseController extra_props = {} extra_props[:goal_form] = goal_form if goal_form - render inertia: settings_component_for("goals"), props: settings_page_props( + render_settings_page( active_section: "goals", - settings_update_path: my_settings_goals_path - ).merge(extra_props), status: status + settings_update_path: my_settings_goals_path, + status: status, + extra_props: extra_props + ) + end + + def section_props + { + settings_update_path: my_settings_goals_path, + create_goal_path: my_settings_goals_create_path, + user: user_props, + options: options_props + } end def goal_params diff --git a/app/controllers/settings/integrations_controller.rb b/app/controllers/settings/integrations_controller.rb index b4d890f..5d128a5 100644 --- a/app/controllers/settings/integrations_controller.rb +++ b/app/controllers/settings/integrations_controller.rb @@ -24,6 +24,43 @@ class Settings::IntegrationsController < Settings::BaseController ) end + def section_props + can_enable_slack_status = @user.slack_access_token.present? && @user.slack_scopes.include?("users.profile:write") + enabled_sailors_logs = SailorsLogNotificationPreference.where( + slack_uid: @user.slack_uid, + enabled: true, + ).where.not(slack_channel_id: SailorsLog::DEFAULT_CHANNELS) + channel_ids = enabled_sailors_logs.pluck(:slack_channel_id) + + { + settings_update_path: my_settings_integrations_path, + user: user_props, + slack: { + can_enable_status: can_enable_slack_status, + notification_channels: channel_ids.map { |channel_id| + { + id: channel_id, + label: "##{channel_id}", + url: "https://hackclub.slack.com/archives/#{channel_id}" + } + } + }, + github: { + connected: @user.github_uid.present?, + username: @user.github_username, + profile_url: (@user.github_username.present? ? "https://github.com/#{@user.github_username}" : nil) + }, + emails: @user.email_addresses.map { |email| + { + email: email.email, + source: email.source&.humanize || "Unknown", + can_unlink: @user.can_delete_email_address?(email) + } + }, + paths: paths_props + } + end + def integrations_params params.require(:user).permit(:uses_slack_status) end diff --git a/app/controllers/settings/notifications_controller.rb b/app/controllers/settings/notifications_controller.rb index 8b23797..57d89a9 100644 --- a/app/controllers/settings/notifications_controller.rb +++ b/app/controllers/settings/notifications_controller.rb @@ -32,4 +32,11 @@ class Settings::NotificationsController < Settings::BaseController status: status ) end + + def section_props + { + settings_update_path: my_settings_notifications_path, + user: user_props + } + end end diff --git a/app/controllers/settings/profile_controller.rb b/app/controllers/settings/profile_controller.rb index c981d6a..6d0955a 100644 --- a/app/controllers/settings/profile_controller.rb +++ b/app/controllers/settings/profile_controller.rb @@ -23,6 +23,16 @@ class Settings::ProfileController < Settings::BaseController ) end + def section_props + { + settings_update_path: my_settings_profile_path, + username_max_length: User::USERNAME_MAX_LENGTH, + user: user_props, + options: options_props, + badges: badges_props + } + end + def profile_params permitted = params.require(:user).permit( :timezone, diff --git a/app/javascript/pages/Home/SignedIn.svelte b/app/javascript/pages/Home/SignedIn.svelte index 5310b9b..2312b4b 100644 --- a/app/javascript/pages/Home/SignedIn.svelte +++ b/app/javascript/pages/Home/SignedIn.svelte @@ -125,9 +125,7 @@ {ssp_users_recent} {ssp_users_size} /> - {/if} - - {#if github_uid_blank} + {:else if github_uid_blank} {/if} diff --git a/app/javascript/pages/Home/signedIn/SetupNotice.svelte b/app/javascript/pages/Home/signedIn/SetupNotice.svelte index 3976677..963da20 100644 --- a/app/javascript/pages/Home/signedIn/SetupNotice.svelte +++ b/app/javascript/pages/Home/signedIn/SetupNotice.svelte @@ -18,10 +18,6 @@
-

- Hello friend! Looks like you are new around here, let's get you set up so - you can start tracking your coding time. -

diff --git a/app/javascript/pages/Home/signedIn/StatCard.svelte b/app/javascript/pages/Home/signedIn/StatCard.svelte index 930aa37..e9fabbe 100644 --- a/app/javascript/pages/Home/signedIn/StatCard.svelte +++ b/app/javascript/pages/Home/signedIn/StatCard.svelte @@ -14,8 +14,7 @@
{#if subtitle} - {subtitle} {/if} diff --git a/app/javascript/pages/OAuthApplications/New.svelte b/app/javascript/pages/OAuthApplications/New.svelte index ebc0512..768049d 100644 --- a/app/javascript/pages/OAuthApplications/New.svelte +++ b/app/javascript/pages/OAuthApplications/New.svelte @@ -1,7 +1,3 @@ - - @@ -108,8 +112,8 @@ id="badge_project" bind:value={selectedProject} items={badges.projects.map((project) => ({ - value: project, - label: project, + value: project.repo_path, + label: project.display_name, }))} />
diff --git a/app/javascript/pages/Users/Settings/Data.svelte b/app/javascript/pages/Users/Settings/Data.svelte index ddf9924..5b40781 100644 --- a/app/javascript/pages/Users/Settings/Data.svelte +++ b/app/javascript/pages/Users/Settings/Data.svelte @@ -1,5 +1,5 @@