Module: PagesHelper

Includes:
UrlsHelper
Included in:
Www::VideoSectionComponent
Defined in:
app/helpers/pages_helper.rb

Instance Method Summary collapse

Methods included from UrlsHelper

#catalog_breadcrumb_links, #catalog_link, #catalog_link_for_product_line, #catalog_link_for_sku, #cms_link, #delocalized_path, #path_to_sales_product_sku, #path_to_sales_product_sku_for_product_line, #path_to_sales_product_sku_for_product_line_slug, #product_line_from_catalog_link, #protocol_neutral_url, #sanitize_external_url, #valid_external_url?

Instance Method Details

#heated_driveway_cost_rowsObject



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'app/helpers/pages_helper.rb', line 166

def heated_driveway_cost_rows
  if canada?
    { range: 'C$3,200 and C$25,000', event_range: 'C$3–C$18', controls: 'C$799–C$5,299',
      sizes: [
        { name: 'Small (10ʹ × 20ʹ)', full_sqft: 200, tt_sqft: 80, full_cost: '~C$3,200', tt_cost: '~C$2,400' },
        { name: 'Standard (20ʹ × 20ʹ)', full_sqft: 400, tt_sqft: 160, full_cost: '~C$6,200', tt_cost: '~C$3,950' },
        { name: 'Large (30ʹ × 20ʹ)', full_sqft: 600, tt_sqft: 240, full_cost: '~C$9,000+', tt_cost: '~C$4,500+' }
      ] }
  else
    { range: '$2,500 and $20,000', event_range: '$3–$20', controls: '$629–$3,799',
      sizes: [
        { name: 'Small (10ʹ × 20ʹ)', full_sqft: 200, tt_sqft: 80, full_cost: '~$2,500', tt_cost: '~$1,850' },
        { name: 'Standard (20ʹ × 20ʹ)', full_sqft: 400, tt_sqft: 160, full_cost: '~$4,750', tt_cost: '~$2,360' },
        { name: 'Large (30ʹ × 20ʹ)', full_sqft: 600, tt_sqft: 240, full_cost: '~$7,000+', tt_cost: '~$3,500+' }
      ] }
  end
end

#page_article_faqs(tag: main_tag) ⇒ Array<Article>

Retrieve FAQs tagged with the given tag, including vote data.

Parameters:

  • tag (String) (defaults to: main_tag)

    The tag to filter by (default: main_tag)

Returns:

  • (Array<Article>)

    Array of FAQ articles with vote data



113
114
115
# File 'app/helpers/pages_helper.rb', line 113

def page_article_faqs(tag: main_tag)
  retrieve_faqs(tags: tag, add_vote_data: true)
end

#page_banner_image(tag: main_tag&.sub(/\Afor-/, 'banner-for-')) ⇒ Image?

Look up the most recent Image tagged with the banner tag for this page.
Derives the banner tag from main_tag by swapping the "for-" prefix to "banner-for-".

Parameters:

  • tag (String, nil) (defaults to: main_tag&.sub(/\Afor-/, 'banner-for-'))

    Override banner tag (default: derived from main_tag)

Returns:

  • (Image, nil)

    The most recent matching image, or nil



160
161
162
163
164
# File 'app/helpers/pages_helper.rb', line 160

def page_banner_image(tag: main_tag&.sub(/\Afor-/, 'banner-for-'))
  return nil if tag.blank?

  Image.tagged_with(tag).order(created_at: :desc).first
end

#page_header_h1(title, options = {}) ⇒ Object

New: H1 header helper matching our H2/H3 helpers



16
17
18
19
20
21
22
23
24
25
# File 'app/helpers/pages_helper.rb', line 16

def page_header_h1(title, options = {})
  # Default H1 styling to match design system (large, light weight, primary color)
  options[:class] ||= 'text-red fw-light display-4'
  options[:class] << " #{options[:extra_class]}" if options[:extra_class].present?
  options[:data] ||= {}
  options[:data][:swiftype] ||= {}
  options[:data][:swiftype][:name] = 'title'
  options[:data][:swiftype][:type] = 'string'
  tag.h1 title, **options
end

#page_header_h2(title, options = {}) ⇒ Object



3
4
5
6
7
# File 'app/helpers/pages_helper.rb', line 3

def page_header_h2(title, options = {})
  options[:class] ||= 'text-red fw-light'
  options[:class] << " #{options[:extra_class]}" if options[:extra_class].present?
  tag.h2 title, **options
end

#page_header_h3(title, options = {}) ⇒ Object



9
10
11
12
13
# File 'app/helpers/pages_helper.rb', line 9

def page_header_h3(title, options = {})
  options[:class] ||= 'text-red fw-light'
  options[:class] << " #{options[:extra_class]}" if options[:extra_class].present?
  tag.h3 title, **options
end

#page_posts(tag: main_tag, source: nil, limit: 12, backfill_tag: nil, min: nil) ⇒ ActiveRecord::Relation<Post>, Array<Post>

Retrieve published posts tagged with the given tag, ordered by publication date.

When +backfill_tag+ is provided, materialises the primary results and tops up
from the backfill tag when the count falls below +min+ (defaults to +limit+).
Returns an Array in that case; otherwise returns an ActiveRecord relation.

Parameters:

  • tag (String) (defaults to: main_tag)

    The tag to filter by (default: main_tag)

  • limit (Integer) (defaults to: 12)

    Maximum number of posts to return (default: 12)

  • backfill_tag (String, nil) (defaults to: nil)

    Broader tag to fill gaps from

  • min (Integer, nil) (defaults to: nil)

    Minimum count before backfill kicks in (default: limit)

Returns:

  • (ActiveRecord::Relation<Post>, Array<Post>)


85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'app/helpers/pages_helper.rb', line 85

def page_posts(tag: main_tag, source: nil, limit: 12, backfill_tag: nil, min: nil)
  if source.respond_to?(:linked_posts)
    linked = source.linked_posts
    return linked if linked.any?
  end

  primary = Post.published
                .tagged_with(tag)
                .includes(:preview_image, original_author: :profile_image)
                .order(published_at: :desc)
                .limit(limit)

  return primary unless backfill_tag

  results = primary.to_a
  threshold = min || limit
  return results if results.size >= threshold

  backfill = page_posts(tag: backfill_tag, limit: limit - results.size)
               .where.not(id: results.map(&:id))
               .to_a
  results + backfill
end

#page_publications(tag: main_tag) ⇒ Array<Publication>

Retrieve publications (documents) tagged with the given tag in the current locale.

Parameters:

  • tag (String) (defaults to: main_tag)

    The tag to filter by (default: main_tag)

Returns:

  • (Array<Publication>)

    Array of publications



151
152
153
# File 'app/helpers/pages_helper.rb', line 151

def page_publications(tag: main_tag)
  find_publications_by_tag_in_current_locale(tag).to_a
end

#page_showcases(tag: main_tag, limit: 20, backfill_tag: nil, min: nil) ⇒ ActiveRecord::Relation<Showcase>, Array<Showcase>

Retrieve showcases tagged with the given tag, ordered by image count and recency.

When +backfill_tag+ is provided, materialises the primary results and tops up
from the backfill tag when the count falls below +min+ (defaults to +limit+).
Returns an Array in that case; otherwise returns an ActiveRecord relation.

Parameters:

  • tag (String) (defaults to: main_tag)

    The tag to filter by (default: main_tag)

  • limit (Integer) (defaults to: 20)

    Maximum number of showcases to return (default: 20)

  • backfill_tag (String, nil) (defaults to: nil)

    Broader tag to fill gaps from

  • min (Integer, nil) (defaults to: nil)

    Minimum count before backfill kicks in (default: limit)

Returns:



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'app/helpers/pages_helper.rb', line 52

def page_showcases(tag: main_tag, limit: 20, backfill_tag: nil, min: nil)
  primary = Showcase.published
                    .tags_include(tag)
                    .includes(:region_state, showcase_digital_assets: :digital_asset)
                    .left_joins(:showcase_digital_assets)
                    .select('showcases.*, COUNT(showcase_digital_assets.id) AS images_count')
                    .group('showcases.id')
                    .order('images_count DESC, showcases.updated_at DESC')
                    .limit(limit)

  return primary unless backfill_tag

  results = primary.to_a
  threshold = min || limit
  return results if results.size >= threshold

  backfill = page_showcases(tag: backfill_tag, limit: limit - results.size)
               .where.not(id: results.map(&:id))
               .to_a
  results + backfill
end

#page_videos(tag: main_tag, limit: 20, backfill_tag: nil, min: nil) ⇒ ActiveRecord::Relation<Video>, Array<Video>

Retrieve public videos tagged with the given tag, ordered by creation date.

When +backfill_tag+ is provided, materialises the primary results and tops up
from the backfill tag when the count falls below +min+ (defaults to +limit+).
Returns an Array in that case; otherwise returns an ActiveRecord relation.

Parameters:

  • tag (String) (defaults to: main_tag)

    The tag to filter by (default: main_tag)

  • limit (Integer) (defaults to: 20)

    Maximum number of videos to return (default: 20)

  • backfill_tag (String, nil) (defaults to: nil)

    Broader tag to fill gaps from

  • min (Integer, nil) (defaults to: nil)

    Minimum count before backfill kicks in (default: limit)

Returns:



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'app/helpers/pages_helper.rb', line 128

def page_videos(tag: main_tag, limit: 20, backfill_tag: nil, min: nil)
  primary = Video.public_videos
                 .tagged_with(tag)
                 .includes(:poster_image)
                 .order(created_at: :desc)
                 .limit(limit)

  return primary unless backfill_tag

  results = primary.to_a
  threshold = min || limit
  return results if results.size >= threshold

  backfill = page_videos(tag: backfill_tag, limit: limit - results.size)
               .where.not(id: results.map(&:id))
               .to_a
  results + backfill
end

#review_benefit_cardObject

Dynamic benefit card for BenefitsListComponent (homepage "Why Choose Us").
Uses the same cached company-wide stats as review_trust_badge.



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'app/helpers/pages_helper.rb', line 202

def review_benefit_card
  stats = ReviewsIo.company_wide_stats
  return nil unless stats[:satisfaction_pct].present?

  count_label = if stats[:num] >= 1000
                  "#{(stats[:num] / 100) * 100}+"
                else
                  stats[:num].to_s
                end

  {
    # Localized path; BenefitsListComponent also wraps with cms_link (idempotent for /paths).
    url: cms_link('/reviews'),
    icon: 'trophy-star',
    title: 'Unparalleled Customer Satisfaction',
    description: "#{stats[:satisfaction_pct]}% satisfaction from #{count_label} reviews"
  }
end

#review_trust_badgeObject

Dynamic trust badge for company-wide review stats (cached 7 days).
Returns a hash compatible with banner_badges in FullWidthLandingPageHeaderComponent.



186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'app/helpers/pages_helper.rb', line 186

def review_trust_badge
  stats = ReviewsIo.company_wide_stats
  return nil unless stats[:star_avg].present? && stats[:num].positive?

  count_label = if stats[:num] >= 1000
                  "#{(stats[:num] / 100) * 100}+ Reviews"
                else
                  "#{stats[:num]} Reviews"
                end

  { icon: 'star', icon_family: 'fas', icon_class: 'text-warning',
    label: "#{stats[:star_avg]}/5", subtitle: count_label, url: '#reviews' }
end