cms42 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README.md +299 -0
  4. data/Rakefile +10 -0
  5. data/app/assets/stylesheets/cms/application.css +3 -0
  6. data/app/controllers/cms/admin/api_keys_controller.rb +52 -0
  7. data/app/controllers/cms/admin/base_controller.rb +54 -0
  8. data/app/controllers/cms/admin/documents_controller.rb +56 -0
  9. data/app/controllers/cms/admin/form_fields_controller.rb +70 -0
  10. data/app/controllers/cms/admin/form_submissions_controller.rb +36 -0
  11. data/app/controllers/cms/admin/images_controller.rb +67 -0
  12. data/app/controllers/cms/admin/pages_controller.rb +188 -0
  13. data/app/controllers/cms/admin/sections_controller.rb +177 -0
  14. data/app/controllers/cms/admin/sites_controller.rb +60 -0
  15. data/app/controllers/cms/admin/webhook_deliveries_controller.rb +19 -0
  16. data/app/controllers/cms/admin/webhooks_controller.rb +51 -0
  17. data/app/controllers/cms/api/base_controller.rb +45 -0
  18. data/app/controllers/cms/api/v1/base_controller.rb +29 -0
  19. data/app/controllers/cms/api/v1/pages_controller.rb +21 -0
  20. data/app/controllers/cms/api/v1/sites_controller.rb +18 -0
  21. data/app/controllers/cms/application_controller.rb +11 -0
  22. data/app/controllers/cms/public/base_controller.rb +23 -0
  23. data/app/controllers/cms/public/form_submissions_controller.rb +63 -0
  24. data/app/controllers/cms/public/previews_controller.rb +21 -0
  25. data/app/controllers/cms/public/sites_controller.rb +37 -0
  26. data/app/controllers/concerns/cms/admin/page_scoped_sections.rb +42 -0
  27. data/app/controllers/concerns/cms/current_site_resolver.rb +17 -0
  28. data/app/controllers/concerns/cms/public/page_paths.rb +31 -0
  29. data/app/controllers/concerns/cms/public/page_rendering.rb +23 -0
  30. data/app/controllers/concerns/cms/site_resolvable.rb +27 -0
  31. data/app/helpers/cms/admin/pages_helper.rb +29 -0
  32. data/app/helpers/cms/admin/sections_helper.rb +86 -0
  33. data/app/helpers/cms/admin/sites_helper.rb +8 -0
  34. data/app/helpers/cms/application_helper.rb +51 -0
  35. data/app/helpers/cms/media_helper.rb +28 -0
  36. data/app/helpers/cms/pages_helper.rb +16 -0
  37. data/app/helpers/cms/sections_helper.rb +25 -0
  38. data/app/helpers/cms/sites_helper.rb +11 -0
  39. data/app/javascript/cms/controllers/sortable_controller.js +38 -0
  40. data/app/jobs/cms/application_job.rb +6 -0
  41. data/app/jobs/cms/deliver_webhook_job.rb +66 -0
  42. data/app/mailers/cms/application_mailer.rb +9 -0
  43. data/app/mailers/cms/form_submission_mailer.rb +16 -0
  44. data/app/models/cms/api_key.rb +27 -0
  45. data/app/models/cms/application_record.rb +7 -0
  46. data/app/models/cms/document.rb +48 -0
  47. data/app/models/cms/form_field.rb +27 -0
  48. data/app/models/cms/form_submission.rb +42 -0
  49. data/app/models/cms/image.rb +61 -0
  50. data/app/models/cms/image_translation.rb +22 -0
  51. data/app/models/cms/page.rb +228 -0
  52. data/app/models/cms/page_section.rb +43 -0
  53. data/app/models/cms/page_translation.rb +22 -0
  54. data/app/models/cms/section/block_base.rb +32 -0
  55. data/app/models/cms/section/blocks/call_to_action_block.rb +16 -0
  56. data/app/models/cms/section/blocks/hero_block.rb +14 -0
  57. data/app/models/cms/section/blocks/image_block.rb +13 -0
  58. data/app/models/cms/section/blocks/rich_text_block.rb +12 -0
  59. data/app/models/cms/section/kind_registry.rb +66 -0
  60. data/app/models/cms/section.rb +94 -0
  61. data/app/models/cms/section_image.rb +10 -0
  62. data/app/models/cms/section_translation.rb +25 -0
  63. data/app/models/cms/site.rb +87 -0
  64. data/app/models/cms/webhook.rb +41 -0
  65. data/app/models/cms/webhook_delivery.rb +12 -0
  66. data/app/serializers/cms/api/base_serializer.rb +51 -0
  67. data/app/serializers/cms/api/page_serializer.rb +145 -0
  68. data/app/serializers/cms/api/site_serializer.rb +45 -0
  69. data/app/services/cms/locale_resolver.rb +30 -0
  70. data/app/services/cms/page_resolver.rb +73 -0
  71. data/app/services/cms/public_page_context.rb +49 -0
  72. data/app/views/cms/admin/api_keys/_form.html.erb +23 -0
  73. data/app/views/cms/admin/api_keys/create.html.erb +9 -0
  74. data/app/views/cms/admin/api_keys/edit.html.erb +5 -0
  75. data/app/views/cms/admin/api_keys/index.html.erb +36 -0
  76. data/app/views/cms/admin/api_keys/new.html.erb +5 -0
  77. data/app/views/cms/admin/documents/_form.html.erb +24 -0
  78. data/app/views/cms/admin/documents/edit.html.erb +2 -0
  79. data/app/views/cms/admin/documents/index.html.erb +37 -0
  80. data/app/views/cms/admin/documents/new.html.erb +2 -0
  81. data/app/views/cms/admin/form_fields/_form.html.erb +46 -0
  82. data/app/views/cms/admin/form_fields/edit.html.erb +2 -0
  83. data/app/views/cms/admin/form_fields/index.html.erb +41 -0
  84. data/app/views/cms/admin/form_fields/new.html.erb +2 -0
  85. data/app/views/cms/admin/form_submissions/index.html.erb +38 -0
  86. data/app/views/cms/admin/images/_form.html.erb +36 -0
  87. data/app/views/cms/admin/images/edit.html.erb +2 -0
  88. data/app/views/cms/admin/images/index.html.erb +25 -0
  89. data/app/views/cms/admin/images/new.html.erb +2 -0
  90. data/app/views/cms/admin/pages/_attach_section_panel.html.erb +20 -0
  91. data/app/views/cms/admin/pages/_form.html.erb +116 -0
  92. data/app/views/cms/admin/pages/_section_editor_frame.html.erb +3 -0
  93. data/app/views/cms/admin/pages/_sections_list.html.erb +9 -0
  94. data/app/views/cms/admin/pages/edit.html.erb +2 -0
  95. data/app/views/cms/admin/pages/index.html.erb +62 -0
  96. data/app/views/cms/admin/pages/new.html.erb +2 -0
  97. data/app/views/cms/admin/pages/show.html.erb +111 -0
  98. data/app/views/cms/admin/sections/_form.html.erb +128 -0
  99. data/app/views/cms/admin/sections/_section.html.erb +22 -0
  100. data/app/views/cms/admin/sections/edit.html.erb +9 -0
  101. data/app/views/cms/admin/sections/index.html.erb +47 -0
  102. data/app/views/cms/admin/sections/new.html.erb +9 -0
  103. data/app/views/cms/admin/sections/page_update.turbo_stream.erb +17 -0
  104. data/app/views/cms/admin/sections/show.html.erb +97 -0
  105. data/app/views/cms/admin/sites/_form.html.erb +44 -0
  106. data/app/views/cms/admin/sites/edit.html.erb +3 -0
  107. data/app/views/cms/admin/sites/new.html.erb +5 -0
  108. data/app/views/cms/admin/sites/show.html.erb +22 -0
  109. data/app/views/cms/admin/webhook_deliveries/index.html.erb +29 -0
  110. data/app/views/cms/admin/webhooks/_form.html.erb +38 -0
  111. data/app/views/cms/admin/webhooks/edit.html.erb +5 -0
  112. data/app/views/cms/admin/webhooks/index.html.erb +34 -0
  113. data/app/views/cms/admin/webhooks/new.html.erb +5 -0
  114. data/app/views/cms/form_submission_mailer/notify.html.erb +14 -0
  115. data/app/views/cms/form_submission_mailer/notify.text.erb +7 -0
  116. data/app/views/cms/public/pages/_content.html.erb +48 -0
  117. data/app/views/cms/public/pages/show.html.erb +44 -0
  118. data/app/views/cms/public/pages/templates/_custom.html.erb +3 -0
  119. data/app/views/cms/public/pages/templates/_form.html.erb +3 -0
  120. data/app/views/cms/public/pages/templates/_landing.html.erb +3 -0
  121. data/app/views/cms/public/pages/templates/_standard.html.erb +3 -0
  122. data/app/views/cms/sections/kinds/_cta.html.erb +13 -0
  123. data/app/views/cms/sections/kinds/_hero.html.erb +14 -0
  124. data/app/views/cms/sections/kinds/_image.html.erb +19 -0
  125. data/app/views/cms/sections/kinds/_rich_text.html.erb +6 -0
  126. data/app/views/layouts/cms/application.html.erb +13 -0
  127. data/app/views/layouts/cms/public.html.erb +14 -0
  128. data/bin/rails +19 -0
  129. data/bin/rubocop +9 -0
  130. data/cms.gemspec +29 -0
  131. data/config/importmap.rb +4 -0
  132. data/config/locales/activerecord.cms.en.yml +65 -0
  133. data/config/locales/en.yml +390 -0
  134. data/config/routes.rb +56 -0
  135. data/lib/cms/engine.rb +45 -0
  136. data/lib/cms/version.rb +5 -0
  137. data/lib/cms.rb +75 -0
  138. data/lib/cms42.rb +3 -0
  139. data/lib/generators/cms/install/install_generator.rb +26 -0
  140. data/lib/generators/cms/install/templates/create_cms_tables.rb +194 -0
  141. data/lib/generators/cms/install/templates/initializer.rb +21 -0
  142. data/lib/generators/cms/views/views_generator.rb +79 -0
  143. data/lib/tasks/cms_tasks.rake +6 -0
  144. data/lib/tasks/version.rake +8 -0
  145. metadata +281 -0
@@ -0,0 +1,111 @@
1
+ <% crumbs = breadcrumbs_for(@page) %>
2
+ <% if crumbs.length > 1 %>
3
+ <nav class="cms-breadcrumbs">
4
+ <% crumbs.each_with_index do |crumb, i| %>
5
+ <% if i < crumbs.length - 1 %>
6
+ <%= link_to crumb.display_title, admin_page_path(crumb) %> &rsaquo;
7
+ <% else %>
8
+ <span><%= crumb.display_title %></span>
9
+ <% end %>
10
+ <% end %>
11
+ </nav>
12
+ <% end %>
13
+
14
+ <h1><%= @page.display_title %></h1>
15
+
16
+ <% if @page.parent %>
17
+ <p><strong><%= t(".parent") %>:</strong> <%= link_to @page.parent.display_title, admin_page_path(@page.parent) %></p>
18
+ <% end %>
19
+ <% if @subpages.any? %>
20
+ <p>
21
+ <strong><%= t(".subpages") %>:</strong>
22
+ <% @subpages.each do |sub| %>
23
+ <%= link_to sub.display_title, admin_page_path(sub) %>
24
+ <% end %>
25
+ </p>
26
+ <% end %>
27
+
28
+ <p>
29
+ <%= link_to t(".back"), admin_pages_path(locale: @translation_locale) %>
30
+ |
31
+ <%= link_to t(".edit"), edit_admin_page_path(@page, locale: @translation_locale) %>
32
+ |
33
+ <%= link_to t(".section_library"), admin_sections_path(locale: @translation_locale) %>
34
+ |
35
+ <%= link_to t(".preview"), preview_admin_page_path(@page), target: "_blank" %>
36
+ |
37
+ <%= link_to t(".share_preview"), page_preview_url(@page.preview_token), target: "_blank" %>
38
+ <% if @page.template_key == "form" %>
39
+ |
40
+ <%= link_to t(".form_fields"), admin_page_form_fields_path(@page) %>
41
+ |
42
+ <%= link_to t(".submissions"), admin_page_form_submissions_path(@page) %>
43
+ <% end %>
44
+ </p>
45
+
46
+ <p>
47
+ <strong><%= t(".translations") %>:</strong>
48
+ <% @page.page_translations.each do |t| %>
49
+ <%= link_to t.locale.upcase, admin_page_path(@page, locale: t.locale) %>
50
+ <% end %>
51
+ &nbsp;
52
+ <% translation_completeness(@page).each do |locale, status| %>
53
+ <% unless @page.page_translations.map(&:locale).include?(locale) %>
54
+ <span title="<%= t(".translation_missing_title", locale: locale) %>">[<%= t(".translation_missing_badge", locale: locale.upcase) %>]</span>
55
+ <% end %>
56
+ <% end %>
57
+ </p>
58
+ <p><strong><%= t(".locale") %>:</strong> <%= @translation_locale %></p>
59
+ <p><strong><%= t(".slug") %>:</strong> <%= @page.slug %></p>
60
+ <p><strong><%= t(".template") %>:</strong> <%= cms_page_template_label(@page.template_key) %></p>
61
+ <p><strong><%= t(".status") %>:</strong> <%= cms_page_status_label(@page.status) %></p>
62
+ <p><strong><%= t(".home_page") %>:</strong> <%= cms_yes_no(@page.home?) %></p>
63
+ <p><strong><%= t(".header") %>:</strong> <%= cms_yes_no(@page.show_in_header?) %></p>
64
+ <p><strong><%= t(".footer") %>:</strong> <%= cms_yes_no(@page.show_in_footer?) %></p>
65
+ <p><strong><%= t(".nav_group") %>:</strong> <%= @page.nav_group %></p>
66
+ <p><strong><%= t(".meta_title") %>:</strong> <%= @page.display_meta_title.presence || t("cms.shared.empty_value") %></p>
67
+ <p><strong><%= t(".meta_description") %>:</strong> <%= @page.seo_description.presence || t("cms.shared.empty_value") %></p>
68
+
69
+ <% if @page.hero_image.attached? %>
70
+ <p><strong><%= t(".hero_image") %>:</strong></p>
71
+ <p><%= image_tag cms_attachment_path(@page.hero_image), alt: @page.display_title, style: "max-width: 420px; height: auto;" %></p>
72
+ <% end %>
73
+
74
+ <% if @page.media_files.attached? %>
75
+ <p><strong><%= t(".attachments") %>:</strong></p>
76
+ <ul>
77
+ <% @page.media_files.each do |file| %>
78
+ <li><%= link_to file.filename.to_s, file %></li>
79
+ <% end %>
80
+ </ul>
81
+ <% end %>
82
+
83
+ <%# Sections panel %>
84
+ <section class="cms-sections-panel">
85
+ <h2><%= t(".content_sections") %></h2>
86
+
87
+ <%= render "cms/admin/pages/sections_list",
88
+ page: @page,
89
+ page_sections: @page_sections %>
90
+
91
+ <hr>
92
+
93
+ <details class="cms-add-section">
94
+ <summary class="cms-btn"><%= t(".add_section") %></summary>
95
+ <div class="cms-add-section__kinds">
96
+ <% Cms::Section::KindRegistry.registered_kinds.each do |kind| %>
97
+ <%= link_to cms_section_kind_label(kind),
98
+ new_admin_page_section_path(@page, kind: kind, locale: @translation_locale),
99
+ data: { turbo_frame: "cms-section-form" },
100
+ class: "cms-btn cms-btn--sm" %>
101
+ <% end %>
102
+ </div>
103
+ </details>
104
+
105
+ <%# Turbo Frame target for inline section forms %>
106
+ <%= render "cms/admin/pages/section_editor_frame" %>
107
+
108
+ <%= render "cms/admin/pages/attach_section_panel",
109
+ page: @page,
110
+ available_sections: @available_sections %>
111
+ </section>
@@ -0,0 +1,128 @@
1
+ <%= form_with model: @section, url: (
2
+ if @page
3
+ @section.new_record? ? admin_page_sections_path(@page) : admin_page_section_path(@page, @section)
4
+ else
5
+ @section.new_record? ? admin_sections_path : admin_section_path(@section)
6
+ end
7
+ ) do |f| %>
8
+ <% if @section.errors.any? %>
9
+ <div class="cms-form__errors">
10
+ <ul>
11
+ <% @section.errors.full_messages.each do |msg| %>
12
+ <li><%= msg %></li>
13
+ <% end %>
14
+ </ul>
15
+ </div>
16
+ <% end %>
17
+
18
+ <%= f.hidden_field :kind %>
19
+
20
+ <div class="cms-form__field">
21
+ <label class="cms-form__label"><%= t(".kind") %></label>
22
+ <span class="cms-badge"><%= cms_section_kind_label(@section.kind) %></span>
23
+ </div>
24
+
25
+ <div class="cms-form__field">
26
+ <%= f.label :enabled, class: "cms-form__label" %>
27
+ <%= f.check_box :enabled, class: "cms-checkbox" %>
28
+ </div>
29
+
30
+ <% translation = @section.translations.find { |record| record.locale == @translation_locale.to_s } || @section.translations.build(locale: @translation_locale) %>
31
+
32
+ <% case @section.kind %>
33
+ <% when "rich_text" %>
34
+ <%= f.fields_for :translations, translation do |tf| %>
35
+ <%= tf.hidden_field :id if tf.object.persisted? %>
36
+ <%= tf.hidden_field :locale, value: @translation_locale %>
37
+
38
+ <div class="cms-form__field">
39
+ <%= tf.label :title, class: "cms-form__label" %>
40
+ <%= tf.text_field :title, class: "cms-input" %>
41
+ </div>
42
+
43
+ <div class="cms-form__field">
44
+ <%= tf.label :content, t(".content"), class: "cms-form__label" %>
45
+ <%= tf.rich_text_area :content, class: "cms-input cms-input--textarea" %>
46
+ </div>
47
+ <% end %>
48
+ <% when "image" %>
49
+ <div class="cms-form__field">
50
+ <%= label_tag "section_image_ids", t(".select_images"), class: "cms-form__label" %>
51
+ <%= hidden_field_tag "section[image_ids][]", "" %>
52
+ <% cms_section_image_options(@section.site).each do |label, value| %>
53
+ <div>
54
+ <%= check_box_tag "section[image_ids][]", value, Array(@section.image_ids).map(&:to_s).include?(value.to_s) %>
55
+ <%= label_tag nil, label %>
56
+ </div>
57
+ <% end %>
58
+ <%= image_library_actions %>
59
+ </div>
60
+ <% when "hero" %>
61
+ <div class="cms-form__field">
62
+ <%= f.label :background_color, class: "cms-form__label" %>
63
+ <%= f.color_field :background_color, class: "cms-input" %>
64
+ </div>
65
+
66
+ <div class="cms-form__field">
67
+ <%= f.label :cta_url, class: "cms-form__label" %>
68
+ <%= f.url_field :cta_url, class: "cms-input" %>
69
+ </div>
70
+
71
+ <%= f.fields_for :translations, translation do |tf| %>
72
+ <%= tf.hidden_field :id if tf.object.persisted? %>
73
+ <%= tf.hidden_field :locale, value: @translation_locale %>
74
+
75
+ <div class="cms-form__field">
76
+ <%= tf.label :title, class: "cms-form__label" %>
77
+ <%= tf.text_field :title, class: "cms-input" %>
78
+ </div>
79
+
80
+ <div class="cms-form__field">
81
+ <%= tf.label :subtitle, t(".cta_text"), class: "cms-form__label" %>
82
+ <%= tf.text_field :subtitle, class: "cms-input" %>
83
+ </div>
84
+
85
+ <div class="cms-form__field">
86
+ <%= tf.label :content, t(".content"), class: "cms-form__label" %>
87
+ <%= tf.rich_text_area :content, class: "cms-input cms-input--textarea" %>
88
+ </div>
89
+ <% end %>
90
+ <% when "cta" %>
91
+ <div class="cms-form__field">
92
+ <%= f.label :button_url, class: "cms-form__label" %>
93
+ <%= f.url_field :button_url, class: "cms-input" %>
94
+ </div>
95
+
96
+ <div class="cms-form__field">
97
+ <%= f.label :alignment, class: "cms-form__label" %>
98
+ <%= f.select :alignment, [["Left", "left"], ["Center", "center"], ["Right", "right"]], {}, class: "cms-input" %>
99
+ </div>
100
+
101
+ <%= f.fields_for :translations, translation do |tf| %>
102
+ <%= tf.hidden_field :id if tf.object.persisted? %>
103
+ <%= tf.hidden_field :locale, value: @translation_locale %>
104
+
105
+ <div class="cms-form__field">
106
+ <%= tf.label :title, class: "cms-form__label" %>
107
+ <%= tf.text_field :title, class: "cms-input" %>
108
+ </div>
109
+
110
+ <div class="cms-form__field">
111
+ <%= tf.label :subtitle, t(".button_text"), class: "cms-form__label" %>
112
+ <%= tf.text_field :subtitle, class: "cms-input" %>
113
+ </div>
114
+
115
+ <div class="cms-form__field">
116
+ <%= tf.label :content, t(".content"), class: "cms-form__label" %>
117
+ <%= tf.rich_text_area :content, class: "cms-input cms-input--textarea" %>
118
+ </div>
119
+ <% end %>
120
+ <% end %>
121
+
122
+ <div class="cms-form__actions">
123
+ <%= f.submit @section.new_record? ? t(".add") : t(".update"), class: "cms-btn cms-btn--primary" %>
124
+ <%= link_to t(".cancel"),
125
+ (@page ? admin_page_path(@page, locale: @translation_locale) : admin_sections_path(locale: @translation_locale)),
126
+ class: "cms-btn" %>
127
+ </div>
128
+ <% end %>
@@ -0,0 +1,22 @@
1
+ <div class="cms-section-item"
2
+ id="page-section-<%= page_section.id %>"
3
+ data-page-section-id="<%= page_section.id %>">
4
+ <span class="cms-section-item__handle" title="<%= t(".drag") %>">☰</span>
5
+
6
+ <span class="cms-badge cms-badge--kind"><%= cms_section_kind_label(page_section.section.kind) %></span>
7
+ <span class="cms-section-item__title"><%= page_section.section.title.presence || t(".no_title") %></span>
8
+ <span class="cms-badge <%= page_section.section.enabled? ? 'cms-badge--enabled' : 'cms-badge--disabled' %>">
9
+ <%= page_section.section.enabled? ? t(".enabled") : t(".disabled") %>
10
+ </span>
11
+
12
+ <span class="cms-section-item__actions">
13
+ <%= link_to t(".edit"),
14
+ edit_admin_page_section_path(@page, page_section.section, locale: @translation_locale),
15
+ data: { turbo_frame: "cms-section-form" },
16
+ class: "cms-btn cms-btn--sm" %>
17
+ <%= link_to t(".delete"),
18
+ admin_page_section_path(@page, page_section.section),
19
+ data: { turbo_method: :delete, turbo_confirm: t(".delete_confirm") },
20
+ class: "cms-btn cms-btn--sm cms-btn--danger" %>
21
+ </span>
22
+ </div>
@@ -0,0 +1,9 @@
1
+ <% if @page %>
2
+ <%= turbo_frame_tag "cms-section-form" do %>
3
+ <h2><%= t(".title", kind: cms_section_kind_label(@section.kind)) %></h2>
4
+ <%= render "form" %>
5
+ <% end %>
6
+ <% else %>
7
+ <h1><%= t(".title", kind: cms_section_kind_label(@section.kind)) %></h1>
8
+ <%= render "form" %>
9
+ <% end %>
@@ -0,0 +1,47 @@
1
+ <h1><%= t(".title") %></h1>
2
+
3
+ <p>
4
+ <%= link_to t(".new"), new_admin_section_path(locale: params[:locale]) %>
5
+ |
6
+ <%= link_to t(".back"), admin_pages_path(locale: params[:locale]) %>
7
+ </p>
8
+
9
+ <p><%= t(".intro") %></p>
10
+
11
+ <% if @sections.empty? %>
12
+ <p><%= t(".empty") %></p>
13
+ <% else %>
14
+ <table>
15
+ <thead>
16
+ <tr>
17
+ <th><%= t(".kind") %></th>
18
+ <th><%= t(".title_heading") %></th>
19
+ <th><%= t(".usage") %></th>
20
+ <th><%= t(".status") %></th>
21
+ <th><%= t(".actions") %></th>
22
+ </tr>
23
+ </thead>
24
+ <tbody>
25
+ <% @sections.each do |section| %>
26
+ <tr>
27
+ <td><%= cms_section_kind_label(section.kind) %></td>
28
+ <td><%= section.title.presence || t(".no_title") %></td>
29
+ <td>
30
+ <% if section.pages.any? %>
31
+ <%= section.pages.map(&:display_title).join(", ") %>
32
+ <% else %>
33
+ <%= t(".unused") %>
34
+ <% end %>
35
+ </td>
36
+ <td><%= section.enabled? ? t(".enabled") : t(".disabled") %></td>
37
+ <td>
38
+ <%= link_to t(".edit"), edit_admin_section_path(section, locale: params[:locale]) %>
39
+ <%= link_to t(".delete"),
40
+ admin_section_path(section, locale: params[:locale]),
41
+ data: { turbo_method: :delete, turbo_confirm: t(".delete_confirm") } %>
42
+ </td>
43
+ </tr>
44
+ <% end %>
45
+ </tbody>
46
+ </table>
47
+ <% end %>
@@ -0,0 +1,9 @@
1
+ <% if @page %>
2
+ <%= turbo_frame_tag "cms-section-form" do %>
3
+ <h2><%= t(".title", kind: cms_section_kind_label(@section.kind)) %></h2>
4
+ <%= render "form" %>
5
+ <% end %>
6
+ <% else %>
7
+ <h1><%= t(".title", kind: cms_section_kind_label(@section.kind)) %></h1>
8
+ <%= render "form" %>
9
+ <% end %>
@@ -0,0 +1,17 @@
1
+ <%= turbo_stream.replace "cms-sections-list" do %>
2
+ <%= render "cms/admin/pages/sections_list",
3
+ page: @page,
4
+ page_sections: @page_sections %>
5
+ <% end %>
6
+
7
+ <%= turbo_stream.replace "cms-section-form" do %>
8
+ <%= render "cms/admin/pages/section_editor_frame" %>
9
+ <% end %>
10
+
11
+ <%= turbo_stream.replace "cms-attach-section-panel" do %>
12
+ <%= render "cms/admin/pages/attach_section_panel",
13
+ page: @page,
14
+ available_sections: @available_sections %>
15
+ <% end %>
16
+
17
+ <%= turbo_stream.replace "flash", partial: "shared/alerts" %>
@@ -0,0 +1,97 @@
1
+ <% content_for :page_title, "#{@section.title.presence || t("cms.admin.sections.index.no_title")} - CMS" %>
2
+ <% content_for :title, @section.title.presence || t("cms.admin.sections.index.no_title") %>
3
+
4
+ <div class="cms-stack">
5
+ <section class="cms-panel">
6
+ <h2><%= t("cms.admin.sections.show.details") %></h2>
7
+
8
+ <p><strong><%= t("cms.admin.sections.index.kind") %>:</strong> <span class="cms-badge"><%= cms_section_kind_label(@section.kind) %></span></p>
9
+ <p><strong><%= t("cms.admin.sections.index.status") %>:</strong> <%= @section.enabled? ? t("cms.admin.sections.index.enabled") : t("cms.admin.sections.index.disabled") %></p>
10
+ <p><strong><%= t("cms.admin.sections.show.global") %>:</strong> <%= cms_yes_no(@section.global?) %></p>
11
+
12
+ <% if @section.kind == "hero" %>
13
+ <h3><%= t("cms.admin.sections.form.settings") %></h3>
14
+ <% if @section.background_color.present? %>
15
+ <p><strong><%= Cms::Section.human_attribute_name(:background_color) %>:</strong> <code><%= @section.background_color %></code></p>
16
+ <% end %>
17
+ <% if @section.cta_url.present? %>
18
+ <p><strong><%= Cms::Section.human_attribute_name(:cta_url) %>:</strong> <code><%= @section.cta_url %></code></p>
19
+ <% end %>
20
+ <% elsif @section.kind == "cta" %>
21
+ <h3><%= t("cms.admin.sections.form.settings") %></h3>
22
+ <% if @section.button_url.present? %>
23
+ <p><strong><%= Cms::Section.human_attribute_name(:button_url) %>:</strong> <code><%= @section.button_url %></code></p>
24
+ <% end %>
25
+ <% if @section.alignment.present? %>
26
+ <p><strong><%= Cms::Section.human_attribute_name(:alignment) %>:</strong> <%= @section.alignment.capitalize %></p>
27
+ <% end %>
28
+ <% end %>
29
+
30
+ <h3><%= t("cms.admin.sections.index.usage") %></h3>
31
+ <% if @section.pages.any? %>
32
+ <ul>
33
+ <% @section.pages.each do |page| %>
34
+ <li><%= link_to page.display_title, admin_page_path(page, locale: @translation_locale) %></li>
35
+ <% end %>
36
+ </ul>
37
+ <% else %>
38
+ <p><%= t("cms.admin.sections.index.unused") %></p>
39
+ <% end %>
40
+ </section>
41
+
42
+ <section class="cms-panel">
43
+ <h2><%= t("cms.admin.pages.show.translations") %></h2>
44
+ <% locales = [@translation_locale, *@section.available_locales].compact.uniq %>
45
+ <% translations_by_locale = @section.translations.index_by(&:locale) %>
46
+
47
+ <% locales.each do |locale| %>
48
+ <% translation = translations_by_locale[locale.to_s] %>
49
+ <article class="cms-panel">
50
+ <p><strong><%= t("locales.#{locale}", default: locale.to_s.upcase) %></strong></p>
51
+ <% if translation %>
52
+ <p><strong><%= t("cms.admin.sections.form.title") %>:</strong> <%= translation.title.presence || t("cms.shared.empty_value") %></p>
53
+
54
+ <% if @section.kind == "hero" && translation.subtitle.present? %>
55
+ <p><strong><%= t("cms.admin.sections.form.cta_text") %>:</strong> <%= translation.subtitle %></p>
56
+ <% end %>
57
+
58
+ <% if @section.kind == "cta" && translation.subtitle.present? %>
59
+ <p><strong><%= t("cms.admin.sections.form.button_text") %>:</strong> <%= translation.subtitle %></p>
60
+ <% end %>
61
+
62
+ <p><strong><%= t("cms.admin.sections.form.content") %>:</strong></p>
63
+ <% if translation.content.present? %>
64
+ <div class="cms-panel"><%= translation.content %></div>
65
+ <% else %>
66
+ <p><%= t("cms.shared.empty_value") %></p>
67
+ <% end %>
68
+ <% else %>
69
+ <p><%= t("cms.admin.sections.show.no_translation") %></p>
70
+ <% end %>
71
+ </article>
72
+ <% end %>
73
+
74
+ <% if @section.kind == "image" && @section.image_assets.any? %>
75
+ <section class="cms-panel">
76
+ <h3><%= t("cms.admin.sections.form.select_images") %></h3>
77
+ <div class="cms-grid">
78
+ <% @section.image_assets.each do |image| %>
79
+ <article>
80
+ <%= image_tag image.file, alt: image.alt_text.presence || image.display_title, style: "max-width: 100%; height: auto;" %>
81
+ <p><%= image.display_title %></p>
82
+ </article>
83
+ <% end %>
84
+ </div>
85
+ </section>
86
+ <% end %>
87
+ </section>
88
+ </div>
89
+
90
+ <div class="cms-form__actions">
91
+ <%= link_to t("cms.admin.sections.index.back", default: "Back"), admin_sections_path(locale: @translation_locale), class: "cms-btn" %>
92
+ <%= link_to t("cms.admin.sections.index.edit"), edit_admin_section_path(@section, locale: @translation_locale), class: "cms-btn cms-btn--primary" %>
93
+ <%= link_to t("cms.admin.sections.index.delete"),
94
+ admin_section_path(@section, locale: @translation_locale),
95
+ class: "cms-btn",
96
+ data: { turbo_method: :delete, turbo_confirm: t("cms.admin.sections.index.delete_confirm") } %>
97
+ </div>
@@ -0,0 +1,44 @@
1
+ <%= form_with model: @site, url: admin_site_path, method: @site.persisted? ? :patch : :post do |f| %>
2
+ <p>
3
+ <%= f.label :name %><br>
4
+ <%= f.text_field :name %>
5
+ </p>
6
+
7
+ <p>
8
+ <%= f.label :slug %><br>
9
+ <%= f.text_field :slug %>
10
+ </p>
11
+
12
+ <p>
13
+ <%= f.label :default_locale %><br>
14
+ <%= f.text_field :default_locale %>
15
+ </p>
16
+
17
+ <p>
18
+ <%= f.label :published %>
19
+ <%= f.check_box :published %>
20
+ </p>
21
+
22
+ <p>
23
+ <%= f.label :logo %><br>
24
+ <%= f.file_field :logo, accept: "image/*" %>
25
+ <br>
26
+ <small><%= t("cms.admin.sites.form.logo_hint") %></small>
27
+ <% if @site.logo.attached? %>
28
+ <br>
29
+ <%= image_tag cms_attachment_path(@site.logo), alt: t("cms.admin.sites.form.logo_alt", site: @site.name), style: "max-width: 200px; height: auto;" %>
30
+ <br>
31
+ <label>
32
+ <%= check_box_tag "site[remove_logo]", "1" %>
33
+ <%= t("cms.admin.sites.form.remove_logo") %>
34
+ </label>
35
+ <% end %>
36
+ </p>
37
+
38
+ <p>
39
+ <%= f.submit submit_label %>
40
+ <% if cancel_path.present? %>
41
+ <%= link_to t("cms.admin.sites.form.cancel"), cancel_path %>
42
+ <% end %>
43
+ </p>
44
+ <% end %>
@@ -0,0 +1,3 @@
1
+ <h1><%= t("cms.admin.sites.edit.title") %></h1>
2
+
3
+ <%= render "form", submit_label: t("cms.admin.sites.form.save"), cancel_path: admin_site_path %>
@@ -0,0 +1,5 @@
1
+ <h1><%= t("cms.admin.sites.new.title") %></h1>
2
+
3
+ <p><%= t("cms.admin.sites.new.intro") %></p>
4
+
5
+ <%= render "form", submit_label: t("cms.admin.sites.form.create"), cancel_path: nil %>
@@ -0,0 +1,22 @@
1
+ <h1><%= t("cms.admin.sites.show.title") %></h1>
2
+
3
+ <p>
4
+ <%= t("cms.admin.sites.show.public_url") %>:
5
+ <%= link_to site_path(@site.slug), site_path(@site.slug) %>
6
+ </p>
7
+
8
+ <p><strong><%= t("cms.admin.sites.show.published") %>:</strong> <%= cms_yes_no(@site.published?) %></p>
9
+ <p><strong><%= t("cms.admin.sites.show.default_locale") %>:</strong> <%= @site.default_locale %></p>
10
+ <p><strong><%= t("cms.admin.sites.show.available_locales") %>:</strong> <%= I18n.available_locales.map(&:to_s).join(", ") %></p>
11
+
12
+ <% if @site.logo.attached? %>
13
+ <p><strong><%= t("cms.admin.sites.show.logo") %>:</strong></p>
14
+ <p><%= image_tag cms_attachment_path(@site.logo), alt: t("cms.admin.sites.show.logo_alt", site: @site.name), style: "max-width: 220px; height: auto;" %></p>
15
+ <p><small><%= t("cms.admin.sites.show.logo_hint") %></small></p>
16
+ <% end %>
17
+
18
+ <p>
19
+ <%= link_to t("cms.admin.sites.show.edit"), edit_admin_site_path %>
20
+ |
21
+ <%= link_to t("cms.admin.sites.show.manage_pages"), admin_pages_path %>
22
+ </p>
@@ -0,0 +1,29 @@
1
+ <h1><%= t("cms.admin.webhook_deliveries.index.title", url: @webhook.url) %></h1>
2
+ <p><%= link_to t("cms.admin.webhook_deliveries.index.back"), admin_webhooks_path %></p>
3
+
4
+ <% if @deliveries.empty? %>
5
+ <p><%= t("cms.admin.webhook_deliveries.index.empty") %></p>
6
+ <% else %>
7
+ <table>
8
+ <thead>
9
+ <tr>
10
+ <th><%= t("cms.admin.webhook_deliveries.index.delivered_at") %></th>
11
+ <th><%= t("cms.admin.webhook_deliveries.index.event") %></th>
12
+ <th><%= t("cms.admin.webhook_deliveries.index.status") %></th>
13
+ <th><%= t("cms.admin.webhook_deliveries.index.response_code") %></th>
14
+ <th><%= t("cms.admin.webhook_deliveries.index.error") %></th>
15
+ </tr>
16
+ </thead>
17
+ <tbody>
18
+ <% @deliveries.each do |delivery| %>
19
+ <tr>
20
+ <td><%= cms_datetime(delivery.delivered_at) %></td>
21
+ <td><%= delivery.event %></td>
22
+ <td><%= delivery.success? ? t("cms.shared.yes") : t("cms.shared.no") %></td>
23
+ <td><%= delivery.response_code.presence || t("cms.shared.empty_value") %></td>
24
+ <td><%= delivery.error_message.presence || t("cms.shared.empty_value") %></td>
25
+ </tr>
26
+ <% end %>
27
+ </tbody>
28
+ </table>
29
+ <% end %>
@@ -0,0 +1,38 @@
1
+ <%= form_with model: [:admin, webhook] do |f| %>
2
+ <% if webhook.errors.any? %>
3
+ <div>
4
+ <% webhook.errors.full_messages.each do |msg| %>
5
+ <p><%= msg %></p>
6
+ <% end %>
7
+ </div>
8
+ <% end %>
9
+
10
+ <div>
11
+ <%= f.label :url, t(".url") %>
12
+ <%= f.url_field :url %>
13
+ </div>
14
+
15
+ <div>
16
+ <%= f.label :secret, t(".secret") %>
17
+ <%= f.text_field :secret %>
18
+ </div>
19
+
20
+ <div>
21
+ <p><strong><%= t(".events") %></strong></p>
22
+ <% Cms::Webhook::EVENTS.each do |event| %>
23
+ <label>
24
+ <%= check_box_tag "webhook[events][]", event, webhook.events.include?(event) %>
25
+ <%= cms_webhook_event_label(event) %>
26
+ </label>
27
+ <% end %>
28
+ </div>
29
+
30
+ <div>
31
+ <%= f.label :active %>
32
+ <%= f.check_box :active %>
33
+ </div>
34
+
35
+ <div>
36
+ <%= f.submit %>
37
+ </div>
38
+ <% end %>
@@ -0,0 +1,5 @@
1
+ <h1><%= t(".title") %></h1>
2
+
3
+ <%= render "form", webhook: @webhook %>
4
+
5
+ <p><%= link_to t(".back"), admin_webhooks_path %></p>
@@ -0,0 +1,34 @@
1
+ <h1><%= t(".title") %></h1>
2
+
3
+ <p><%= link_to t(".new"), new_admin_webhook_path %></p>
4
+
5
+ <% if @webhooks.any? %>
6
+ <table>
7
+ <thead>
8
+ <tr>
9
+ <th><%= t(".url") %></th>
10
+ <th><%= t(".events") %></th>
11
+ <th><%= t(".active") %></th>
12
+ <th></th>
13
+ </tr>
14
+ </thead>
15
+ <tbody>
16
+ <% @webhooks.each do |webhook| %>
17
+ <tr>
18
+ <td><%= webhook.url %></td>
19
+ <td><%= webhook.events.map { |event| cms_webhook_event_label(event) }.join(", ") %></td>
20
+ <td><%= cms_yes_no(webhook.active?) %></td>
21
+ <td>
22
+ <%= link_to t(".edit"), edit_admin_webhook_path(webhook) %>
23
+ |
24
+ <%= link_to t(".delete"),
25
+ admin_webhook_path(webhook),
26
+ data: { turbo_method: :delete, turbo_confirm: t(".delete_confirm") } %>
27
+ </td>
28
+ </tr>
29
+ <% end %>
30
+ </tbody>
31
+ </table>
32
+ <% else %>
33
+ <p><%= t(".empty") %></p>
34
+ <% end %>
@@ -0,0 +1,5 @@
1
+ <h1><%= t(".title") %></h1>
2
+
3
+ <%= render "form", webhook: @webhook %>
4
+
5
+ <p><%= link_to t(".back"), admin_webhooks_path %></p>