Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/assets/images/sort.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,9 @@ def post_path_for(post)
def cross_domain_url(domain_type, path = "/")
return path unless Rails.env.production?

# No Warden context (e.g. rendering from a background job broadcast)
return path unless respond_to?(:request) && request.present? && request.env["warden"].present?

domains = Rails.application.config.x.domains
host = (domain_type == :primary) ? domains.primary : domains.community

Expand Down
18 changes: 10 additions & 8 deletions app/javascript/controllers/char_counter_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,16 @@ export default class extends Controller {
colorClass = `${pill} bg-red-100 text-red-600`
}

if (isValid) {
this.submitTarget.disabled = false
this.submitTarget.classList.remove("opacity-50", "cursor-not-allowed")
this.submitTarget.classList.add("cursor-pointer", "hover:bg-red-700")
} else {
this.submitTarget.disabled = true
this.submitTarget.classList.add("opacity-50", "cursor-not-allowed")
this.submitTarget.classList.remove("cursor-pointer", "hover:bg-red-700")
if (this.hasSubmitTarget) {
if (isValid) {
this.submitTarget.disabled = false
this.submitTarget.classList.remove("opacity-50", "cursor-not-allowed")
this.submitTarget.classList.add("cursor-pointer", "hover:bg-red-700")
} else {
this.submitTarget.disabled = true
this.submitTarget.classList.add("opacity-50", "cursor-not-allowed")
this.submitTarget.classList.remove("cursor-pointer", "hover:bg-red-700")
}
}

this.counterTargets.forEach(counter => {
Expand Down
12 changes: 12 additions & 0 deletions app/jobs/generate_testimonial_fields_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def perform(testimonial)
unless parsed
Rails.logger.error "Failed to generate testimonial fields for testimonial #{testimonial.id}"
testimonial.update!(ai_feedback: "We couldn't process your testimonial right now. Please try again later.")
broadcast_update(testimonial)
return
end

Expand All @@ -36,6 +37,7 @@ def perform(testimonial)

unless parsed
testimonial.update!(ai_feedback: "We couldn't process your testimonial right now. Please try again later.")
broadcast_update(testimonial)
return
end

Expand All @@ -48,6 +50,7 @@ def perform(testimonial)
rescue JSON::ParserError => e
Rails.logger.error "Failed to parse AI response for testimonial #{testimonial.id}: #{e.message}"
testimonial.update!(ai_feedback: "We couldn't process your testimonial right now. Please try again later.")
broadcast_update(testimonial)
end

private
Expand Down Expand Up @@ -100,6 +103,15 @@ def generate_fields(system_prompt, user_prompt)
result ? JSON.parse(result) : nil
end

def broadcast_update(testimonial)
Turbo::StreamsChannel.broadcast_replace_to(
"testimonial_#{testimonial.id}",
target: "testimonial_section",
partial: "testimonials/section",
locals: { testimonial: testimonial, user: testimonial.user }
)
end

def heading_taken?(heading, testimonial_id)
Testimonial.where.not(id: testimonial_id).exists?(heading: heading)
end
Expand Down
4 changes: 2 additions & 2 deletions app/models/project.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ def stars_gained
[ snapshots.first - snapshots.last, 0 ].max
end

def record_snapshot!
def record_snapshot!(force: false)
snapshot = star_snapshots.find_or_initialize_by(recorded_on: Date.current)
snapshot.update!(stars: stars)
snapshot.update!(stars: stars) if snapshot.new_record? || force
snapshot
end
end
30 changes: 15 additions & 15 deletions app/services/github_data_fetcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -166,11 +166,11 @@ def self.update_user_from_graphql(user, profile_data, repos_data)
user.update!(
username: profile_data[:login],
email: profile_data[:email] || user.email,
name: profile_data[:name],
bio: profile_data[:bio],
name: profile_data[:name] || user.name,
bio: profile_data[:bio] || user.bio,
company: profile_data[:company],
website: profile_data[:websiteUrl].presence,
twitter: profile_data[:twitterUsername].presence,
website: profile_data[:websiteUrl].presence || user.website,
twitter: profile_data[:twitterUsername].presence || user.twitter,
location: profile_data[:location],
avatar_url: profile_data[:avatarUrl],
github_data_updated_at: Time.current
Expand All @@ -190,11 +190,11 @@ def self.update_user_from_graphql(user, profile_data, repos_data)
}
end

sync_projects!(user, repos)
sync_projects!(user, repos, force_snapshot: true)
end

# Sync GitHub repos to Project records with star snapshot tracking
def self.sync_projects!(user, repos_data)
def self.sync_projects!(user, repos_data, force_snapshot: false)
current_urls = repos_data.map { |r| r[:github_url] || r[:url] }

# Soft-archive projects no longer returned by GitHub
Expand All @@ -216,7 +216,7 @@ def self.sync_projects!(user, repos_data)
)

project.save!
project.record_snapshot!
project.record_snapshot!(force: force_snapshot)
end

# Recalculate cached stats on user
Expand Down Expand Up @@ -255,11 +255,11 @@ def update_from_oauth_data
user.update!(
username: auth_data.info.nickname,
email: auth_data.info.email,
name: raw_info.name,
bio: raw_info.bio,
name: raw_info.name || user.name,
bio: raw_info.bio || user.bio,
company: raw_info.company,
website: raw_info.blog.presence,
twitter: raw_info.twitter_username.presence,
website: raw_info.blog.presence || user.website,
twitter: raw_info.twitter_username.presence || user.twitter,
location: raw_info.location,
avatar_url: auth_data.info.image
)
Expand All @@ -286,11 +286,11 @@ def update_from_api
user.update!(
username: data["login"],
email: data["email"] || user.email,
name: data["name"],
bio: data["bio"],
name: data["name"] || user.name,
bio: data["bio"] || user.bio,
company: data["company"],
website: data["blog"],
twitter: data["twitter_username"],
website: data["blog"].presence || user.website,
twitter: data["twitter_username"] || user.twitter,
location: data["location"],
avatar_url: data["avatar_url"]
)
Expand Down
2 changes: 1 addition & 1 deletion app/views/home/_testimonial_carousel.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@

<%# User tile - extra top margin on mobile/tablet to account for speech bubble tail %>
<div class="lg:flex-1 lg:basis-0 flex items-end mt-2 lg:mt-0">
<%= render "users/user_tile", user: testimonial.user, frameless: true, avatar_size: "w-20 h-[72px] md:w-36 md:h-[130px]", compact: true %>
<%= render "users/user_tile", user: testimonial.user, frameless: true, avatar_size: "w-20 h-[72px] md:w-36 md:h-[130px]", compact: true, badge_size: "xs" %>
</div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
<noscript><img src="https://nullitics.com/n.gif?u=<%= request.original_url %><%= "&c=#{country_code}" if country_code.present? %>" alt="" style="position:absolute;left:-9999px" /></noscript>

<% community_nav = content_for?(:nav_community_style) || (controller_name == 'users' && ['index', 'show'].include?(action_name)) %>
<nav class="<%= community_nav ? 'bg-gray-50 pt-4 [@media(min-width:640px)_and_(min-height:500px)]:pt-6' : 'bg-white shadow-sm py-4' %> sticky top-0 z-50" data-controller="mobile-menu" data-action="click@window->mobile-menu#hide">
<nav class="<%= community_nav ? 'bg-gray-50 pt-4 [@media(min-width:640px)_and_(min-height:500px)]:pt-6' : 'bg-white shadow-sm py-4' %> sticky top-0 z-50" <%= 'data-controller="mobile-menu" data-action="click@window->mobile-menu#hide"'.html_safe unless content_for?(:nav_minimal) %>>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<% centered_mobile_nav = content_for?(:nav_community_style) || (controller_name == 'users' && ['index', 'show'].include?(action_name)) %>
<div class="flex items-center <%= centered_mobile_nav ? 'h-14 [@media(min-width:640px)_and_(min-height:500px)]:h-20 justify-center' : 'h-16 justify-between' %>">
Expand Down
5 changes: 5 additions & 0 deletions app/views/shared/_gem_avatar.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<% show_badge = local_assigns.key?(:show_open_to_work) ? show_open_to_work : user.open_to_work? %>
<% small_badge = local_assigns[:badge_size] == "small" %>
<% tiny_badge = local_assigns[:badge_size] == "tiny" %>
<% xs_badge = local_assigns[:badge_size] == "xs" %>
<% map_badge = local_assigns[:badge_size] == "map" %>
<% thick_border = local_assigns[:thick_border] == true %>
<div class="<%= size %> flex-shrink-0 relative overflow-visible">
Expand Down Expand Up @@ -45,6 +46,10 @@
<div class="absolute -bottom-[1px] left-1/2 -translate-x-1/2 z-10">
<span class="inline-block px-[3px] py-[0.5px] text-[5px] font-bold uppercase tracking-wide text-white bg-red-600 rounded-full whitespace-nowrap" style="line-height:1.2;letter-spacing:0.3px;">Open to work</span>
</div>
<% elsif xs_badge %>
<div class="absolute -bottom-[1px] md:bottom-1 left-1/2 -translate-x-1/2 z-10">
<span class="inline-block px-[3px] md:px-2 py-[0.5px] md:py-0.5 text-[5px] md:text-[10px] font-bold uppercase leading-[1.2] tracking-[0.3px] text-white bg-red-600 rounded-full whitespace-nowrap shadow-sm">Open to work</span>
</div>
<% else %>
<div class="absolute <%= small_badge ? 'bottom-0' : 'bottom-1' %> left-1/2 -translate-x-1/2 z-10">
<span class="inline-block <%= small_badge ? 'px-1.5 py-px text-[8px]' : 'px-2 py-0.5 text-[10px]' %> font-bold uppercase tracking-wide text-white bg-red-600 rounded-full whitespace-nowrap shadow-sm">
Expand Down
34 changes: 17 additions & 17 deletions app/views/users/_profile_settings.html.erb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<div id="<%= local_assigns[:wrapper_id] || 'profile_settings' %>" class="md:space-y-3">
<div id="<%= local_assigns[:wrapper_id] || 'profile_settings' %>" class="space-y-0">
<%# Public Profile Toggle %>
<div class="flex items-center justify-between gap-4 py-3 md:py-0 border-t border-gray-100 md:border-t-0">
<div class="flex items-center justify-between gap-4 py-3">
<span class="text-sm font-medium text-gray-500">Public profile</span>
<%= button_to toggle_public_user_settings_path,
method: :post,
Expand All @@ -12,34 +12,34 @@
<% end %>
</div>

<%# Open to Work Toggle %>
<div class="flex items-center justify-between gap-4 py-3 md:py-0 border-t border-gray-100 md:border-t-0">
<span class="text-sm font-medium text-gray-500">Open to work</span>
<%= button_to toggle_open_to_work_user_settings_path,
<%# Newsletter Toggle %>
<div class="flex items-center justify-between gap-4 py-3 border-t border-gray-100">
<span class="text-sm font-medium text-gray-500">Receive news</span>
<%= button_to toggle_newsletter_user_settings_path,
method: :post,
class: "relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 #{user.open_to_work? ? 'bg-red-600' : 'bg-gray-400'}",
class: "relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 #{user.unsubscribed_from_newsletter? ? 'bg-gray-400' : 'bg-red-600'}",
data: { turbo_stream: true } do %>
<span class="sr-only">Toggle open to work</span>
<span class="sr-only">Toggle newsletter subscription</span>
<span aria-hidden="true"
class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out <%= user.open_to_work? ? 'translate-x-4' : 'translate-x-0' %>"></span>
class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out <%= user.unsubscribed_from_newsletter? ? 'translate-x-0' : 'translate-x-4' %>"></span>
<% end %>
</div>

<%# Newsletter Toggle %>
<div class="flex items-center justify-between gap-4 py-3 md:py-0 border-t border-gray-100 md:border-t-0">
<span class="text-sm font-medium text-gray-500">Newsletter</span>
<%= button_to toggle_newsletter_user_settings_path,
<%# Open to Work Toggle %>
<div class="flex items-center justify-between gap-4 py-3 border-t border-gray-100">
<span class="text-sm font-medium text-gray-500">Open to work</span>
<%= button_to toggle_open_to_work_user_settings_path,
method: :post,
class: "relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 #{user.unsubscribed_from_newsletter? ? 'bg-gray-400' : 'bg-red-600'}",
class: "relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 #{user.open_to_work? ? 'bg-red-600' : 'bg-gray-400'}",
data: { turbo_stream: true } do %>
<span class="sr-only">Toggle newsletter subscription</span>
<span class="sr-only">Toggle open to work</span>
<span aria-hidden="true"
class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out <%= user.unsubscribed_from_newsletter? ? 'translate-x-0' : 'translate-x-4' %>"></span>
class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out <%= user.open_to_work? ? 'translate-x-4' : 'translate-x-0' %>"></span>
<% end %>
</div>

<%# Sign Out Button %>
<div class="md:pt-0">
<div class="pt-1">
<%= button_to "Sign out", destroy_user_session_path,
method: :delete,
data: { turbo: false },
Expand Down
10 changes: 5 additions & 5 deletions app/views/users/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
<span class="text-5xl md:text-6xl lg:text-7xl font-black text-red-600 tabular-nums"><%= number_with_delimiter(@total_users_count) %></span>
<h1 class="text-2xl md:text-3xl lg:text-4xl font-bold text-gray-900"><%= t('users.index.developer_count', count: @total_users_count) %></h1>
<%= link_to community_user_url(current_user), class: "group sm:hidden ml-auto self-center", data: { turbo_frame: "_top" } do %>
<%= render "shared/gem_avatar", user: current_user, size: "w-10 h-9", badge_size: "map", thick_border: true %>
<%= render "shared/gem_avatar", user: current_user, size: "w-12 h-[43px]", badge_size: "tiny", thick_border: true %>
<% end %>
</div>
<p class="text-lg md:text-xl text-gray-500 max-w-2xl">
Expand Down Expand Up @@ -106,7 +106,7 @@
<% if user_signed_in? %>
<div class="hidden sm:flex sm:flex-col sm:items-end mt-4 mb-1">
<%= link_to community_user_url(current_user), class: "group", data: { turbo_frame: "_top" } do %>
<%= render "shared/gem_avatar", user: current_user, size: "w-10 h-9", badge_size: "map", thick_border: true %>
<%= render "shared/gem_avatar", user: current_user, size: "w-12 h-[43px]", badge_size: "tiny", thick_border: true %>
<% end %>
</div>
<% unless current_user.testimonial&.published? %>
Expand All @@ -128,8 +128,8 @@
<span class="text-sm font-medium <%= @filter_open_to_work ? 'text-gray-700' : 'text-gray-400' %>">Open to work</span>
<% end %>
<div class="relative" data-controller="dropdown">
<button type="button" class="flex items-center gap-x-1 text-sm rounded-full cursor-pointer bg-gray-100 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 px-3 py-2" data-action="click->dropdown#toggle click@window->dropdown#hide">
<span class="text-gray-400"><%= t('users.index.sort_label') %>:</span>
<button type="button" class="flex items-center gap-x-2 text-sm rounded-full cursor-pointer bg-gray-100 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 px-3 py-2" data-action="click->dropdown#toggle click@window->dropdown#hide">
<%= inline_svg_tag "sort.svg", class: "w-4 h-4 text-gray-400" %>
<span class="text-gray-700 font-medium">
<% effective_sort = (@sort == 'old' ? 'new' : @sort) %>
<% effective_dir = (@sort == 'old' ? 'asc' : @dir) %>
Expand All @@ -145,7 +145,7 @@
end %>
<%= closed_label %>
</span>
<%= inline_svg_tag "chevron-down.svg", class: "ml-1 h-5 w-5 text-gray-400" %>
<%= inline_svg_tag "chevron-down.svg", class: "h-5 w-5 text-gray-400" %>
</button>

<div class="hidden absolute z-50 mt-2 w-64 divide-y divide-gray-100 rounded-md bg-white shadow-lg ring-1 ring-black/5 focus:outline-none left-0 md:left-auto md:right-0 origin-top-left md:origin-top-right" data-dropdown-target="menu">
Expand Down
Loading