|
| 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