|  | 
|  | 1 | +# frozen_string_literal: true | 
|  | 2 | + | 
|  | 3 | +module ReactOnRails | 
|  | 4 | +  module ProHelper | 
|  | 5 | +    IMMEDIATE_HYDRATION_PRO_WARNING = "[REACT ON RAILS] The 'immediate_hydration' feature requires a " \ | 
|  | 6 | +                                      "React on Rails Pro license. " \ | 
|  | 7 | +                                      "Please visit https://shakacode.com/react-on-rails-pro to learn more." | 
|  | 8 | + | 
|  | 9 | +    # Generates the complete component specification script tag. | 
|  | 10 | +    # Handles both immediate hydration (Pro feature) and standard cases. | 
|  | 11 | +    def generate_component_script(render_options) | 
|  | 12 | +      # Setup the page_loaded_js, which is the same regardless of prerendering or not! | 
|  | 13 | +      # The reason is that React is smart about not doing extra work if the server rendering did its job. | 
|  | 14 | +      component_specification_tag = content_tag(:script, | 
|  | 15 | +                                                json_safe_and_pretty(render_options.client_props).html_safe, | 
|  | 16 | +                                                type: "application/json", | 
|  | 17 | +                                                class: "js-react-on-rails-component", | 
|  | 18 | +                                                id: "js-react-on-rails-component-#{render_options.dom_id}", | 
|  | 19 | +                                                "data-component-name" => render_options.react_component_name, | 
|  | 20 | +                                                "data-trace" => (render_options.trace ? true : nil), | 
|  | 21 | +                                                "data-dom-id" => render_options.dom_id, | 
|  | 22 | +                                                "data-store-dependencies" => | 
|  | 23 | +                                                  render_options.store_dependencies&.to_json, | 
|  | 24 | +                                                "data-immediate-hydration" => | 
|  | 25 | +                                                  (render_options.immediate_hydration ? true : nil)) | 
|  | 26 | + | 
|  | 27 | +      # Add immediate invocation script if immediate hydration is enabled | 
|  | 28 | +      spec_tag = if render_options.immediate_hydration | 
|  | 29 | +                   # Escape dom_id for JavaScript context | 
|  | 30 | +                   escaped_dom_id = escape_javascript(render_options.dom_id) | 
|  | 31 | +                   immediate_script = content_tag(:script, %( | 
|  | 32 | +          typeof ReactOnRails === 'object' && ReactOnRails.reactOnRailsComponentLoaded('#{escaped_dom_id}'); | 
|  | 33 | +        ).html_safe) | 
|  | 34 | +                   "#{component_specification_tag}\n#{immediate_script}" | 
|  | 35 | +                 else | 
|  | 36 | +                   component_specification_tag | 
|  | 37 | +                 end | 
|  | 38 | + | 
|  | 39 | +      pro_warning_badge = pro_warning_badge_if_needed(render_options.explicitly_disabled_pro_options) | 
|  | 40 | +      "#{pro_warning_badge}\n#{spec_tag}".html_safe | 
|  | 41 | +    end | 
|  | 42 | + | 
|  | 43 | +    # Generates the complete store hydration script tag. | 
|  | 44 | +    # Handles both immediate hydration (Pro feature) and standard cases. | 
|  | 45 | +    def generate_store_script(redux_store_data) | 
|  | 46 | +      pro_options_check_result = ReactOnRails::ProUtils.disable_pro_render_options_if_not_licensed(redux_store_data) | 
|  | 47 | +      redux_store_data = pro_options_check_result[:raw_options] | 
|  | 48 | +      explicitly_disabled_pro_options = pro_options_check_result[:explicitly_disabled_pro_options] | 
|  | 49 | + | 
|  | 50 | +      store_hydration_data = content_tag(:script, | 
|  | 51 | +                                         json_safe_and_pretty(redux_store_data[:props]).html_safe, | 
|  | 52 | +                                         type: "application/json", | 
|  | 53 | +                                         "data-js-react-on-rails-store" => redux_store_data[:store_name].html_safe, | 
|  | 54 | +                                         "data-immediate-hydration" => | 
|  | 55 | +                                           (redux_store_data[:immediate_hydration] ? true : nil)) | 
|  | 56 | + | 
|  | 57 | +      # Add immediate invocation script if immediate hydration is enabled and Pro license is valid | 
|  | 58 | +      store_hydration_scripts = if redux_store_data[:immediate_hydration] | 
|  | 59 | +                                  # Escape store_name for JavaScript context | 
|  | 60 | +                                  escaped_store_name = escape_javascript(redux_store_data[:store_name]) | 
|  | 61 | +                                  immediate_script = content_tag(:script, <<~JS.strip_heredoc.html_safe | 
|  | 62 | +                                    typeof ReactOnRails === 'object' && ReactOnRails.reactOnRailsStoreLoaded('#{escaped_store_name}'); | 
|  | 63 | +                                  JS | 
|  | 64 | +                                  ) | 
|  | 65 | +                                  "#{store_hydration_data}\n#{immediate_script}" | 
|  | 66 | +                                else | 
|  | 67 | +                                  store_hydration_data | 
|  | 68 | +                                end | 
|  | 69 | + | 
|  | 70 | +      pro_warning_badge = pro_warning_badge_if_needed(explicitly_disabled_pro_options) | 
|  | 71 | +      "#{pro_warning_badge}\n#{store_hydration_scripts}".html_safe | 
|  | 72 | +    end | 
|  | 73 | + | 
|  | 74 | +    def pro_warning_badge_if_needed(explicitly_disabled_pro_options) | 
|  | 75 | +      return "" unless explicitly_disabled_pro_options.any? | 
|  | 76 | + | 
|  | 77 | +      disabled_features_message = disabled_pro_features_message(explicitly_disabled_pro_options) | 
|  | 78 | +      warning_message = "[REACT ON RAILS] #{disabled_features_message}\n" \ | 
|  | 79 | +                        "Please visit https://shakacode.com/react-on-rails-pro to learn more." | 
|  | 80 | +      puts warning_message | 
|  | 81 | +      Rails.logger.warn warning_message | 
|  | 82 | + | 
|  | 83 | +      tooltip_text = "#{disabled_features_message} Click to learn more." | 
|  | 84 | + | 
|  | 85 | +      <<~HTML.strip | 
|  | 86 | +        <a href="https://shakacode.com/react-on-rails-pro" target="_blank" rel="noopener noreferrer" title="#{tooltip_text}"> | 
|  | 87 | +          <div style="position: fixed; top: 0; right: 0; width: 180px; height: 180px; overflow: hidden; z-index: 9999; pointer-events: none;"> | 
|  | 88 | +            <div style="position: absolute; top: 50px; right: -40px; transform: rotate(45deg); background-color: rgba(220, 53, 69, 0.85); color: white; padding: 7px 40px; text-align: center; font-weight: bold; font-family: sans-serif; font-size: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.3); pointer-events: auto;"> | 
|  | 89 | +              React On Rails Pro Required | 
|  | 90 | +            </div> | 
|  | 91 | +          </div> | 
|  | 92 | +        </a> | 
|  | 93 | +      HTML | 
|  | 94 | +    end | 
|  | 95 | + | 
|  | 96 | +    def disabled_pro_features_message(explicitly_disabled_pro_options) | 
|  | 97 | +      return "".html_safe unless explicitly_disabled_pro_options.any? | 
|  | 98 | + | 
|  | 99 | +      feature_list = explicitly_disabled_pro_options.join(", ") | 
|  | 100 | +      feature_word = explicitly_disabled_pro_options.size == 1 ? "feature" : "features" | 
|  | 101 | +      "The '#{feature_list}' #{feature_word} " \ | 
|  | 102 | +        "#{explicitly_disabled_pro_options.size == 1 ? 'requires' : 'require'} a " \ | 
|  | 103 | +        "React on Rails Pro license. " | 
|  | 104 | +    end | 
|  | 105 | +  end | 
|  | 106 | +end | 
0 commit comments