Module: WwwHelper

Defined in:
app/helpers/www_helper.rb

Overview

View helper: www.

Instance Method Summary collapse

Instance Method Details

#build_canonical_paths(url, locales: %i[en-US en-CA],, parameters: nil, request_url: nil) ⇒ Object

This method generates a set of alternate language tag and canonical tag
see https://www.semrush.com/blog/the-most-common-hreflang-mistakes-infographic/
https://www.rebelytics.com/hreflang-canonical/
https://www.portent.com/blog/seo/implement-hreflang-canonical-tags-correctly.htm
First argument is url, the target url to build canonical paths for
Somethings this url might not be the current page, that's why request_url can be passed to do this check
You only want the alternate links on the canonical page (source of truth) page
second argument are the locales to generate the alternates for
parameters will be used in building the final url



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'app/helpers/www_helper.rb', line 96

def build_canonical_paths(url, locales: %i[en-US en-CA], parameters: nil, request_url: nil)
  parameters = parameters&.delete_if { |_k, v| v.blank? } # Removes empty elements
  capture do
    c_url = cms_link(url, host: WEB_HOSTNAME, parameters: parameters, strip_params: true)
    concat tag.link(rel: 'canonical', href: c_url) + "\n".html_safe
    # We need to check that we are on the canonical url before rendering the alternates
    stripped_request_url = request_url ? cms_link(request_url, host: WEB_HOSTNAME, strip_params: true) : nil
    if stripped_request_url.nil? || c_url == stripped_request_url
      default_english_locale = locales.first
      locales.each do |locale|
        concat tag.link(rel: 'alternate', href: cms_link(url, locale, host: WEB_HOSTNAME, parameters: parameters, strip_params: true), hreflang: locale.downcase) + "\n".html_safe
      end
      concat tag.link(rel: 'alternate', href: cms_link(url, default_english_locale, host: WEB_HOSTNAME, parameters: parameters, strip_params: true), hreflang: 'en') + "\n".html_safe
      concat tag.link(rel: 'alternate', href: cms_link(url, default_english_locale, host: WEB_HOSTNAME, parameters: parameters, strip_params: true), hreflang: 'x-default') + "\n".html_safe
    end
  end
end

#landing_page_instant_quote(component) ⇒ ActiveSupport::SafeBuffer

Renders a landing-page instant-quote builder flush — deliberately WITHOUT a
#page_section wrapper. The hero and the quote form are meant to sit
edge-to-edge; wrapping the quote in page_section adds its default py-4
gap and reintroduces exactly the seam this layout removes. Call this right
below Www::FullWidthLandingPageHeaderComponent so the flush convention
can't be broken by reaching for page_section out of habit.

Parameters:

Returns:

  • (ActiveSupport::SafeBuffer)

    the rendered component markup



69
70
71
# File 'app/helpers/www_helper.rb', line 69

def landing_page_instant_quote(component)
  render(component)
end

#page_section(id: nil, container: 'container-lg', padding: 'py-4', border: :bottom, background: :default, motion: nil, extra_classes: nil, data: {}) { ... } ⇒ Object

Renders a consistent page section wrapper with container, padding, borders, and optional animation

Usage:

<%# Basic usage %>
<%= page_section(id: 'features', border: :bottom) do %>
Features
<% end %>

<%# With motion animation %>
<%= page_section(motion: :fade_up) do %>
Animated content
<% end %>

Parameters:

  • id (String) (defaults to: nil)

    Optional section ID

  • container (String) (defaults to: 'container-lg')

    Container class (default: 'container-lg')

  • padding (String) (defaults to: 'py-4')

    Padding class (default: 'py-4')

  • border (Symbol) (defaults to: :bottom)

    Border style (:none, :top, :bottom, :both) - defaults to :bottom

  • background (Symbol) (defaults to: :default)

    Background style (:default, :light, :mint_green, etc.)

  • motion (Symbol, Hash) (defaults to: nil)

    Motion preset or custom options

  • extra_classes (String) (defaults to: nil)

    Additional CSS classes

  • data (Hash{Symbol => Object}) (defaults to: {})

    Stimulus data attributes

Yields:

  • Section content



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'app/helpers/www_helper.rb', line 27

def page_section(id: nil, container: 'container-lg', padding: 'py-4', border: :bottom,
                 background: :default, motion: nil, extra_classes: nil, data: {}, &)
  # Motion presets for common animations
  motion_opts = case motion
                when :fade_up
                  { animation: 'fadeInUp', distance: 70, duration: 0.8 }
                when :fade_left
                  { animation: 'fadeInLeft', distance: 60, duration: 0.8 }
                when :fade_right
                  { animation: 'fadeInRight', distance: 60, duration: 0.8 }
                when :zoom
                  { animation: 'zoomPop', duration: 0.8 }
                when :pulse
                  { animation: 'pulse', duration: 1.2 }
                when Hash
                  motion
                end

  render(Www::PageSectionComponent.new(
    id: id,
    container: container,
    padding: padding,
    border: border,
    background: background,
    motion: motion_opts,
    extra_classes: extra_classes,
    data: data
  ), &)
end

#report_record_errors(record_errors) ⇒ Object



73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'app/helpers/www_helper.rb', line 73

def report_record_errors(record_errors)
  errors = []
  errors << flash[:error] if flash[:error].present?
  flash[:error] = nil
  [record_errors].flatten.each do |record|
    errors += record.errors.full_messages if record.errors.any?
    record.errors.clear
  end
  return if errors.blank?

  formatted_message = errors.map { |e| tag.span(e, class: 'error_notice') }.join('<br>').html_safe
  "reportError('Errors','#{j(formatted_message)}')".html_safe
end

#roof_deicing_benefitsObject



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'app/helpers/www_helper.rb', line 114

def roof_deicing_benefits
  [
    { icon: 'icicles', title: 'Prevent Ice Dams',
      description: 'Heating cables in valleys and eaves stop ice dams before they form, protecting your roof and interior.' },
    { icon: 'droplet-slash', title: 'Stop Water Damage',
      description: 'Ice dams force water under shingles, causing leaks, mold, and structural rot. Deicing eliminates the root cause.' },
    { icon: 'house-crack', title: 'Protect Your Roof',
      description: 'Heavy ice loads stress gutters, fascia, and shingles—heating cables prevent costly damage.' },
    { icon: 'bolt', title: 'Automatic Operation',
      description: 'Smart sensors detect temperature and moisture, activating the system only when needed.' },
    { icon: 'shield-halved', title: 'Proven Reliability',
      description: 'WarmlyYours deicing cables are UL/CSA listed and backed by a manufacturer warranty.' },
    { icon: 'badge-dollar', title: 'Affordable to Operate',
      description: 'Self-regulating cables adjust output based on temperature, minimizing energy use.' }
  ]
end