OTWO-7618 Implement figma design for organizations show page#1883
OTWO-7618 Implement figma design for organizations show page#1883Niharika1117 merged 4 commits intoOTWO-7546from
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Implements updated Explore Organizations page layout and styling (per Figma), including a new responsive “cards on mobile / table on desktop” org activity view and a standardized icon rendering approach via decorators.
Changes:
- Reworked Explore Orgs page layout/partials and added mobile card rendering for 30-day commit volume.
- Introduced global icon container/letter styling and updated project/org icon decorators to use it.
- Updated orgs page styling (including dark theme adjustments) and added org type sort options.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| config/shared/sort_options.yml | Adds org type filter option set. |
| app/views/projects/_project_index.html.haml | Switches project icon rendering to decorator output. |
| app/views/explore/orgs_by_thirty_day_commit_volume.js.haml | Adds mobile card HTML injection and updated show/hide behavior. |
| app/views/explore/orgs.html.haml | Rebuilds Explore Orgs page layout and header. |
| app/views/explore/_stats_by_sector.html.haml | Updates table wrapper/markup styling hooks. |
| app/views/explore/_orgs_by_30_day_commit_volume.html.haml | Replaces chosen select with a custom dropdown; adds mobile card markup + desktop table class. |
| app/views/explore/_newest_orgs.html.haml | Updates newest orgs layout to card design. |
| app/views/explore/_most_active_orgs.html.haml | Updates most active orgs layout to card design. |
| app/decorators/project_decorator.rb | Extends icon API to pass container class and dimensions behavior. |
| app/decorators/organization_decorator.rb | Extends icon API to pass container class. |
| app/decorators/icon.rb | Wraps icon output in a container div and uses a span letter fallback. |
| app/assets/stylesheets/projects.sass | Removes old per-view project icon CSS; small layout tweaks. |
| app/assets/stylesheets/orgs.sass | Adds extensive new Explore Orgs styling incl. responsive behavior and dark theme overrides. |
| app/assets/stylesheets/explore.sass | Adjusts explore header spacing. |
| app/assets/stylesheets/dark_theme.sass | Updates dark theme border colors for common components. |
| app/assets/stylesheets/base.sass | Adds global icon container + icon-letter styling and a broad icon display override. |
| app/assets/javascripts/orgs.js | Adds dark-theme gauge config and custom dropdown AJAX filtering hook. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| var filterOrgs = function(filterValue) { | ||
| $('.busy#commit_volume_loader').removeClass('hidden') | ||
| $.ajax({ | ||
| url: '/explore/orgs_by_thirty_day_commit_volume?format=js&filter='+ $(this).val(), | ||
| url: '/explore/orgs_by_thirty_day_commit_volume?format=js&filter='+ filterValue, | ||
| type: "GET", | ||
| success: function(){ | ||
| $('#orgs_by_30_days_volume table').toggleClass('hidden') | ||
| $('.busy#commit_volume_loader').toggleClass('hidden') | ||
| $('.busy#commit_volume_loader').addClass('hidden') | ||
| } | ||
| }) | ||
| }) | ||
| }; |
There was a problem hiding this comment.
The view code in this PR computes the current selection from params[:org_type] and emits a hidden input named org_type, but the AJAX request here sends filter=.... This mismatch will cause the selected filter to not round-trip correctly (e.g., refresh/back/initial render won’t reflect the active selection). Use a single parameter name consistently (either change the AJAX query param to org_type or update the view/controller to use filter).
| def image(with_dimensions: true, container_class: 'icon-container') | ||
| container_classes = "#{container_class} #{logo ? 'has-logo' : ''}" | ||
| content = if logo | ||
| width_and_height = with_dimensions ? dimensions : '' | ||
| css_style = "#{width_and_height} border:0 none;" | ||
| image_tag(logo.attachment.url(size), style: css_style, itemprop: 'image', alt: 'img avatar') | ||
| else | ||
| content_tag :p, name.first.capitalize, style: default_style | ||
| content_tag :span, name.first.upcase, class: 'icon-letter' | ||
| end | ||
|
|
||
| content_tag :div, content, class: container_classes |
There was a problem hiding this comment.
This removes the previous ‘broken image’ fallback behavior (the old view used an onerror handler to hide the image and show the letter). With the new implementation, if a logo URL 404s or fails to load, users will see a broken image and no letter fallback. Consider rendering both the <img> and the letter and toggling via CSS/JS, or reintroducing an onerror handler that swaps to the letter fallback and updates/removes the has-logo styling.
| content = if logo | ||
| width_and_height = with_dimensions ? dimensions : '' | ||
| css_style = "#{width_and_height} border:0 none;" | ||
| image_tag(logo.attachment.url(size), style: css_style, itemprop: 'image', alt: 'img avatar') |
There was a problem hiding this comment.
The alt text is hard-coded to 'img avatar', which is not descriptive and will be confusing for screen readers. Use an alt derived from the object name (e.g., organization/project name) or set alt: '' if the image is purely decorative and the adjacent link/text already conveys the name.
| - mobile_cards_html = capture do | ||
| - @org_by_30_day_commits.each do |org_30_day_activity| | ||
| - org = org_30_day_activity.organization | ||
| .org-card | ||
| .org-card-header | ||
| = link_to org.decorate.icon(:small), organization_path(org), title: org.name, class: 'org-card-icon' | ||
| = link_to h(truncate(org.name, length: 24)), organization_path(org), title: org.name, class: 'org-card-name' | ||
| %span.org-card-size= org_30_day_activity.decorate.project_count_text | ||
| .org-card-body | ||
| .org-card-row | ||
| .org-card-label= t '.type' | ||
| .org-card-value= org.org_type_label | ||
| .org-card-row | ||
| .org-card-label= t '.projects' | ||
| .org-card-value= org_30_day_activity.project_count | ||
| .org-card-row | ||
| .org-card-label= t '.affiliates' | ||
| .org-card-value= org_30_day_activity.affiliate_count | ||
| .org-card-row | ||
| .org-card-label= t '30_day_commits' | ||
| .org-card-value= org_30_day_activity.thirty_day_commit_count |
There was a problem hiding this comment.
The mobile card markup is duplicated here (JS response) and in the initial render partial (_orgs_by_30_day_commit_volume.html.haml). This will be hard to keep consistent. Consider extracting the card into a partial (e.g., _org_30_day_activity_card) and rendering it both for the initial page and for the JS response (or have the JS response render the same partial into the mobile container).
| .explore-page-wrapper | ||
| .explore-header-top | ||
| %h1= t('.title') | ||
| = render 'shared/search' | ||
|
|
||
| .explore-page-wrapper | ||
| .orgs-layout | ||
| .left-column | ||
| = render partial: 'most_active_orgs' | ||
| = render partial: 'newest_orgs' | ||
| .right-column | ||
| = render partial: 'orgs_by_30_day_commit_volume' | ||
| = render partial: 'stats_by_sector' |
There was a problem hiding this comment.
There are two sibling .explore-page-wrapper containers. If the wrapper class applies spacing/constraints, this can lead to inconsistent layout and duplicated padding/margins. Prefer a single wrapper that contains both the header and the two-column layout, unless there’s a specific styling reason to split them.
| %tr | ||
| %td.strong= Organization::ORG_TYPES.invert[orgs.org_type] | ||
| %td | ||
| #commit-gauge{ 'data-gauge' => orgs.average_commits.to_s, 'data-gauge-max' => gauge_scale.to_s, class: "gauge-#{orgs.org_type}" } |
There was a problem hiding this comment.
This renders multiple elements with the same id commit-gauge (one per row), which is invalid HTML and can break CSS/JS that expects a unique id. Use a class instead (e.g., .commit-gauge) and update the stylesheet selectors accordingly.
| #commit-gauge{ 'data-gauge' => orgs.average_commits.to_s, 'data-gauge-max' => gauge_scale.to_s, class: "gauge-#{orgs.org_type}" } | |
| .commit-gauge{ 'data-gauge' => orgs.average_commits.to_s, 'data-gauge-max' => gauge_scale.to_s, class: "gauge-#{orgs.org_type}" } |
|
|
||
| // Icon Container - Global Baseline Styling | ||
| .icon-container | ||
| display: inline-flex !important |
There was a problem hiding this comment.
These are very broad, global overrides with !important (especially the a [class^=\"icon-\"] selector), which can unintentionally change layout for unrelated icons across the entire app. If this styling is intended primarily for Explore/Projects pages, consider scoping it under a page wrapper (e.g., #explore_orgs_page, .explore-page-wrapper, etc.) or reducing reliance on !important to minimize site-wide regressions.
| display: inline-flex !important | |
| display: inline-flex |
| %label!= t('.filter') | ||
| :ruby | ||
| current_filter = params[:org_type] || 'all_orgs' | ||
| selected_label = OrgThirtyDayActivity::SORT_TYPES.find { |opt| opt[1] == current_filter }&.first || OrgThirtyDayActivity::SORT_TYPES.first.first | ||
| .custom-sort-dropdown | ||
| %button.sort-dropdown-btn{ type: 'button' } | ||
| %span.selection{ data: { value: current_filter } }= selected_label | ||
| %i.fa.fa-chevron-down | ||
| .dropdown-menu.sort-dropdown-menu | ||
| - OrgThirtyDayActivity::SORT_TYPES.each do |label, value| | ||
| %button.sort-dropdown-item{ type: 'button', data: { value: value }, class: (value == current_filter ? 'active' : '') }= label | ||
| %input{ type: 'hidden', name: 'org_type', value: current_filter, class: 'sort-value-input', id: 'org_type' } |
There was a problem hiding this comment.
Replacing the native <select> with a custom button/menu needs additional accessibility support (ARIA roles/attributes like aria-expanded, aria-controls, proper focus management, keyboard navigation). If possible, keep a real <select> as the accessible baseline and enhance it visually, or fully implement ARIA + keyboard behavior for the custom component.
| %label!= t('.filter') | |
| :ruby | |
| current_filter = params[:org_type] || 'all_orgs' | |
| selected_label = OrgThirtyDayActivity::SORT_TYPES.find { |opt| opt[1] == current_filter }&.first || OrgThirtyDayActivity::SORT_TYPES.first.first | |
| .custom-sort-dropdown | |
| %button.sort-dropdown-btn{ type: 'button' } | |
| %span.selection{ data: { value: current_filter } }= selected_label | |
| %i.fa.fa-chevron-down | |
| .dropdown-menu.sort-dropdown-menu | |
| - OrgThirtyDayActivity::SORT_TYPES.each do |label, value| | |
| %button.sort-dropdown-item{ type: 'button', data: { value: value }, class: (value == current_filter ? 'active' : '') }= label | |
| %input{ type: 'hidden', name: 'org_type', value: current_filter, class: 'sort-value-input', id: 'org_type' } | |
| %label{ for: 'org_type' }!= t('.filter') | |
| :ruby | |
| current_filter = params[:org_type] || 'all_orgs' | |
| .custom-sort-dropdown | |
| %select.sort-value-input{ name: 'org_type', id: 'org_type' } | |
| - OrgThirtyDayActivity::SORT_TYPES.each do |label, value| | |
| %option{ value: value, selected: (value == current_filter) }= label |
No description provided.