ruby_cms 0.1.1 → 0.1.3
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +68 -164
- data/app/components/ruby_cms/admin/admin_page.rb +19 -19
- data/app/components/ruby_cms/admin/admin_page_header.rb +79 -0
- data/app/components/ruby_cms/admin/admin_resource_card.rb +55 -0
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table.rb +4 -4
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_actions.rb +5 -5
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_body.rb +1 -1
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_checkbox_cell.rb +15 -13
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_checkbox_head.rb +13 -11
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_delete_modal.rb +9 -9
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_header.rb +2 -2
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_header_bar.rb +8 -8
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_pagination.rb +9 -9
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_row.rb +3 -4
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_actions.rb +46 -32
- data/app/controllers/ruby_cms/admin/base_controller.rb +10 -4
- data/app/controllers/ruby_cms/admin/content_blocks_controller.rb +4 -3
- data/app/controllers/ruby_cms/admin/locale_controller.rb +2 -1
- data/app/controllers/ruby_cms/admin/user_permissions_controller.rb +25 -7
- data/app/helpers/ruby_cms/content_blocks_helper.rb +7 -3
- data/app/helpers/ruby_cms/settings_helper.rb +32 -20
- data/app/javascript/controllers/ruby_cms/bulk_action_table_controller.js +53 -12
- data/app/models/ruby_cms/permission.rb +38 -9
- data/app/models/ruby_cms/permittable.rb +0 -2
- data/app/services/ruby_cms/analytics/report.rb +37 -3
- data/app/views/layouts/ruby_cms/_admin_sidebar.html.erb +2 -2
- data/app/views/layouts/ruby_cms/admin.html.erb +13 -17
- data/app/views/ruby_cms/admin/analytics/index.html.erb +103 -108
- data/app/views/ruby_cms/admin/analytics/page_details.html.erb +28 -32
- data/app/views/ruby_cms/admin/analytics/partials/_back_button.html.erb +3 -2
- data/app/views/ruby_cms/admin/analytics/partials/_browser_device.html.erb +34 -20
- data/app/views/ruby_cms/admin/analytics/partials/_daily_activity_chart.html.erb +27 -27
- data/app/views/ruby_cms/admin/analytics/partials/_hourly_activity_chart.html.erb +19 -19
- data/app/views/ruby_cms/admin/analytics/partials/_landing_pages.html.erb +21 -0
- data/app/views/ruby_cms/admin/analytics/partials/_os_stats.html.erb +26 -0
- data/app/views/ruby_cms/admin/analytics/partials/_popular_pages.html.erb +34 -0
- data/app/views/ruby_cms/admin/analytics/partials/_recent_activity.html.erb +16 -14
- data/app/views/ruby_cms/admin/analytics/partials/_security_alert.html.erb +3 -3
- data/app/views/ruby_cms/admin/analytics/partials/_top_referrers.html.erb +14 -14
- data/app/views/ruby_cms/admin/analytics/partials/_top_visitors.html.erb +28 -0
- data/app/views/ruby_cms/admin/analytics/partials/_utm_sources.html.erb +21 -0
- data/app/views/ruby_cms/admin/analytics/visitor_details.html.erb +44 -48
- data/app/views/ruby_cms/admin/content_blocks/index.html.erb +0 -11
- data/app/views/ruby_cms/admin/content_blocks/show.html.erb +204 -85
- data/app/views/ruby_cms/admin/settings/index.html.erb +214 -175
- data/app/views/ruby_cms/admin/user_permissions/index.html.erb +32 -2
- data/app/views/ruby_cms/admin/users/_row.html.erb +4 -1
- data/config/locales/en.yml +4 -0
- data/lib/generators/ruby_cms/install_generator.rb +2 -1
- data/lib/ruby_cms/cli.rb +1 -1
- data/lib/ruby_cms/engine.rb +20 -12
- data/lib/ruby_cms/version.rb +1 -1
- data/lib/ruby_cms.rb +24 -0
- data/lib/tasks/admin.rake +120 -0
- data/log/test.log +7284 -0
- metadata +10 -4
- data/app/views/ruby_cms/admin/content_blocks/edit.html.erb +0 -17
- data/lib/tasks/ruby_cms.rake +0 -27
|
@@ -7,114 +7,110 @@
|
|
|
7
7
|
show_security = high_volume || rapid || suspicious_ua
|
|
8
8
|
%>
|
|
9
9
|
<%= admin_page(
|
|
10
|
-
title:
|
|
11
|
-
subtitle: "#{@start_date} → #{@end_date}",
|
|
10
|
+
title: @ip_address,
|
|
11
|
+
subtitle: "Visitor analytics · #{@start_date} → #{@end_date}",
|
|
12
12
|
content_card: false
|
|
13
13
|
) do %>
|
|
14
14
|
<div class="space-y-6">
|
|
15
15
|
<div class="flex items-start justify-between gap-4">
|
|
16
|
-
<div class="
|
|
17
|
-
<p class="text-sm font-medium text-gray-500">IP Address</p>
|
|
18
|
-
<p class="mt-1 text-lg font-semibold tracking-tight text-gray-900 tabular-nums truncate"><%= @ip_address %></p>
|
|
19
|
-
</div>
|
|
20
|
-
<div class="flex items-center gap-2 flex-shrink-0">
|
|
16
|
+
<div class="flex items-center gap-2">
|
|
21
17
|
<%= render "ruby_cms/admin/analytics/partials/security_alert" if show_security %>
|
|
22
|
-
<%= render "ruby_cms/admin/analytics/partials/back_button", path: ruby_cms_admin_analytics_path(start_date: @start_date, end_date: @end_date, period: @period) %>
|
|
23
18
|
</div>
|
|
19
|
+
<%= render "ruby_cms/admin/analytics/partials/back_button", path: ruby_cms_admin_analytics_path(start_date: @start_date, end_date: @end_date, period: @period) %>
|
|
24
20
|
</div>
|
|
25
21
|
|
|
26
|
-
<div class="grid grid-cols-4 gap-4">
|
|
27
|
-
<div class="rounded-lg border border-
|
|
28
|
-
<p class="text-sm font-medium text-
|
|
29
|
-
<p class="mt-2 text-
|
|
22
|
+
<div class="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
|
23
|
+
<div class="rounded-lg border border-border/60 bg-white p-6 shadow-sm">
|
|
24
|
+
<p class="text-sm font-medium text-muted-foreground">Total views</p>
|
|
25
|
+
<p class="mt-2 text-2xl font-semibold tracking-tight text-foreground tabular-nums"><%= number_with_delimiter(@visitor_stats[:total_views]) %></p>
|
|
30
26
|
</div>
|
|
31
|
-
<div class="rounded-lg border border-
|
|
32
|
-
<p class="text-sm font-medium text-
|
|
33
|
-
<p class="mt-2 text-
|
|
27
|
+
<div class="rounded-lg border border-border/60 bg-white p-6 shadow-sm">
|
|
28
|
+
<p class="text-sm font-medium text-muted-foreground">Pages visited</p>
|
|
29
|
+
<p class="mt-2 text-2xl font-semibold tracking-tight text-foreground tabular-nums"><%= number_with_delimiter(@visitor_stats[:unique_pages]) %></p>
|
|
34
30
|
</div>
|
|
35
|
-
<div class="rounded-lg border border-
|
|
36
|
-
<p class="text-sm font-medium text-
|
|
37
|
-
<p class="mt-2 text-base font-semibold text-
|
|
31
|
+
<div class="rounded-lg border border-border/60 bg-white p-6 shadow-sm">
|
|
32
|
+
<p class="text-sm font-medium text-muted-foreground">First visit</p>
|
|
33
|
+
<p class="mt-2 text-base font-semibold text-foreground"><%= @visitor_stats[:first_visit].present? ? l(@visitor_stats[:first_visit], format: :short) : "—" %></p>
|
|
38
34
|
</div>
|
|
39
|
-
<div class="rounded-lg border border-
|
|
40
|
-
<p class="text-sm font-medium text-
|
|
41
|
-
<p class="mt-2 text-base font-semibold text-
|
|
35
|
+
<div class="rounded-lg border border-border/60 bg-white p-6 shadow-sm">
|
|
36
|
+
<p class="text-sm font-medium text-muted-foreground">Last visit</p>
|
|
37
|
+
<p class="mt-2 text-base font-semibold text-foreground"><%= @visitor_stats[:last_visit].present? ? l(@visitor_stats[:last_visit], format: :short) : "—" %></p>
|
|
42
38
|
</div>
|
|
43
39
|
</div>
|
|
44
40
|
|
|
45
41
|
<% first_visit_record = Ahoy::Visit.where(ip: @ip_address, started_at: @start_date.beginning_of_day..@end_date.end_of_day).order(started_at: :asc).first %>
|
|
46
42
|
<% has_meta = first_visit_record && (first_visit_record.os.present? || first_visit_record.device_type.present? || first_visit_record.browser.present? || (first_visit_record.respond_to?(:landing_page) && first_visit_record.landing_page.present?)) %>
|
|
47
43
|
<% if has_meta %>
|
|
48
|
-
<div class="rounded-lg border border-
|
|
49
|
-
<p class="text-sm font-semibold text-
|
|
50
|
-
<div class="mt-4 grid grid-cols-4 gap-4">
|
|
44
|
+
<div class="rounded-lg border border-border/60 bg-white p-6 shadow-sm">
|
|
45
|
+
<p class="text-sm font-semibold text-foreground">Environment</p>
|
|
46
|
+
<div class="mt-4 grid grid-cols-2 lg:grid-cols-4 gap-4">
|
|
51
47
|
<% if first_visit_record.os.present? %>
|
|
52
48
|
<div>
|
|
53
|
-
<p class="text-xs font-medium text-
|
|
54
|
-
<p class="mt-1 text-sm font-medium text-
|
|
49
|
+
<p class="text-xs font-medium text-muted-foreground">OS</p>
|
|
50
|
+
<p class="mt-1 text-sm font-medium text-foreground"><%= first_visit_record.os %></p>
|
|
55
51
|
</div>
|
|
56
52
|
<% end %>
|
|
57
53
|
<% if first_visit_record.device_type.present? %>
|
|
58
54
|
<div>
|
|
59
|
-
<p class="text-xs font-medium text-
|
|
60
|
-
<p class="mt-1 text-sm font-medium text-
|
|
55
|
+
<p class="text-xs font-medium text-muted-foreground">Device</p>
|
|
56
|
+
<p class="mt-1 text-sm font-medium text-foreground"><%= first_visit_record.device_type %></p>
|
|
61
57
|
</div>
|
|
62
58
|
<% end %>
|
|
63
59
|
<% if first_visit_record.browser.present? %>
|
|
64
60
|
<div>
|
|
65
|
-
<p class="text-xs font-medium text-
|
|
66
|
-
<p class="mt-1 text-sm font-medium text-
|
|
61
|
+
<p class="text-xs font-medium text-muted-foreground">Browser</p>
|
|
62
|
+
<p class="mt-1 text-sm font-medium text-foreground"><%= first_visit_record.browser %></p>
|
|
67
63
|
</div>
|
|
68
64
|
<% end %>
|
|
69
65
|
<% if first_visit_record.respond_to?(:landing_page) && first_visit_record.landing_page.present? %>
|
|
70
|
-
<div class="col-span-
|
|
71
|
-
<p class="text-xs font-medium text-
|
|
72
|
-
<p class="mt-1 text-sm font-medium text-
|
|
66
|
+
<div class="col-span-full">
|
|
67
|
+
<p class="text-xs font-medium text-muted-foreground">Landing page</p>
|
|
68
|
+
<p class="mt-1 text-sm font-medium text-foreground truncate" title="<%= first_visit_record.landing_page %>"><%= truncate(first_visit_record.landing_page, length: 120) %></p>
|
|
73
69
|
</div>
|
|
74
70
|
<% end %>
|
|
75
71
|
</div>
|
|
76
72
|
</div>
|
|
77
73
|
<% end %>
|
|
78
74
|
|
|
79
|
-
<div class="rounded-lg border border-
|
|
80
|
-
<div class="px-6 py-4 border-b border-
|
|
81
|
-
<p class="text-sm font-semibold text-
|
|
82
|
-
<p class="text-sm text-
|
|
75
|
+
<div class="rounded-lg border border-border/60 bg-white shadow-sm overflow-hidden">
|
|
76
|
+
<div class="px-6 py-4 border-b border-border/40">
|
|
77
|
+
<p class="text-sm font-semibold text-foreground">Activity</p>
|
|
78
|
+
<p class="text-sm text-muted-foreground">Pages visited by this IP.</p>
|
|
83
79
|
</div>
|
|
84
80
|
|
|
85
81
|
<div class="overflow-x-auto">
|
|
86
82
|
<table class="min-w-full text-sm">
|
|
87
|
-
<thead class="bg-
|
|
88
|
-
<tr class="text-left text-xs font-
|
|
83
|
+
<thead class="bg-muted/30">
|
|
84
|
+
<tr class="text-left text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
|
89
85
|
<th class="px-6 py-3">Page</th>
|
|
90
86
|
<th class="px-6 py-3">Browser</th>
|
|
91
87
|
<th class="px-6 py-3">Referrer</th>
|
|
92
88
|
<th class="px-6 py-3">Time</th>
|
|
93
89
|
</tr>
|
|
94
90
|
</thead>
|
|
95
|
-
<tbody class="divide-y divide-
|
|
91
|
+
<tbody class="divide-y divide-border/40">
|
|
96
92
|
<% if @visitor_views.any? %>
|
|
97
93
|
<% @visitor_views.each do |event| %>
|
|
98
94
|
<% visit = event.visit %>
|
|
99
|
-
<tr class="hover:bg-
|
|
100
|
-
<td class="px-6 py-3 font-medium text-
|
|
95
|
+
<tr class="hover:bg-muted/30 transition-colors">
|
|
96
|
+
<td class="px-6 py-3 font-medium text-foreground">
|
|
101
97
|
<% page_name = event.respond_to?(:page_name) ? event.page_name : nil %>
|
|
102
98
|
<% if page_name.present? %>
|
|
103
99
|
<%= link_to page_name,
|
|
104
100
|
page_details_ruby_cms_admin_analytics_path(page_name:, start_date: @start_date, end_date: @end_date, period: @period),
|
|
105
|
-
class: "text-
|
|
101
|
+
class: "text-primary hover:underline" %>
|
|
106
102
|
<% else %>
|
|
107
|
-
<span class="text-
|
|
103
|
+
<span class="text-muted-foreground">Unknown</span>
|
|
108
104
|
<% end %>
|
|
109
105
|
</td>
|
|
110
|
-
<td class="px-6 py-3 text-
|
|
111
|
-
<td class="px-6 py-3 text-
|
|
112
|
-
<td class="px-6 py-3 text-
|
|
106
|
+
<td class="px-6 py-3 text-foreground"><%= visit&.browser || "Unknown" %></td>
|
|
107
|
+
<td class="px-6 py-3 text-foreground truncate max-w-[26rem]"><%= visit&.referrer.presence || "Direct" %></td>
|
|
108
|
+
<td class="px-6 py-3 text-muted-foreground"><%= time_ago_in_words(event.time) %> ago</td>
|
|
113
109
|
</tr>
|
|
114
110
|
<% end %>
|
|
115
111
|
<% else %>
|
|
116
112
|
<tr>
|
|
117
|
-
<td colspan="4" class="px-6 py-10 text-center text-sm text-
|
|
113
|
+
<td colspan="4" class="px-6 py-10 text-center text-sm text-muted-foreground">No activity recorded for this visitor</td>
|
|
118
114
|
</tr>
|
|
119
115
|
<% end %>
|
|
120
116
|
</tbody>
|
|
@@ -29,18 +29,7 @@
|
|
|
29
29
|
end
|
|
30
30
|
%>
|
|
31
31
|
|
|
32
|
-
<% locale_filter_url = ruby_cms_admin_content_blocks_path %>
|
|
33
|
-
<% locale_filter_html = capture do %>
|
|
34
|
-
<%= tag.div class: "flex items-center gap-1" do %>
|
|
35
|
-
<%= tag.span "Locale:", class: "text-xs font-medium text-gray-500 mr-1" %>
|
|
36
|
-
<% ([nil] + I18n.available_locales.to_a).each do |loc| %>
|
|
37
|
-
<% is_active = (params[:locale].blank? && loc.nil?) || (loc && params[:locale].to_s == loc.to_s) %>
|
|
38
|
-
<%= link_to (loc ? ruby_cms_locale_display_name(loc) : "All"), (loc ? "#{locale_filter_url}?locale=#{loc}" : locale_filter_url), class: "inline-flex items-center rounded-md px-2.5 py-1 text-xs font-medium transition-colors no-underline #{is_active ? 'bg-gray-900 text-white' : 'bg-white border border-gray-200 text-gray-700 hover:bg-gray-50'}", data: { turbo_frame: "admin_table_content" } %>
|
|
39
|
-
<% end %>
|
|
40
|
-
<% end %>
|
|
41
|
-
<% end %>
|
|
42
32
|
<%= render partial: "ruby_cms/admin/shared/bulk_action_table_index", locals: {
|
|
43
|
-
header_filter: locale_filter_html,
|
|
44
33
|
title: "Content blocks",
|
|
45
34
|
collection: @content_blocks || [],
|
|
46
35
|
headers: ["Key", "Locale", "Title", "Type", "Published", { text: "Actions", class: "text-right" }],
|
|
@@ -1,110 +1,229 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
</
|
|
1
|
+
<%= render RubyCms::Admin::AdminPageHeader.new(
|
|
2
|
+
title: @content_block.key,
|
|
3
|
+
subtitle: ruby_cms_locale_display_name(@content_block.locale),
|
|
4
|
+
breadcrumbs: [
|
|
5
|
+
{ label: t("ruby_cms.admin.nav.admin", default: "Admin"), url: ruby_cms_admin_root_path },
|
|
6
|
+
{ label: t("ruby_cms.admin.nav.content_blocks", default: "Content Blocks"), url: ruby_cms_admin_content_blocks_path },
|
|
7
|
+
{ label: @content_block.key }
|
|
8
|
+
]
|
|
9
|
+
) do %>
|
|
10
|
+
<% if @content_block.published? %>
|
|
11
|
+
<span class="inline-flex px-2 py-1 text-xs font-semibold rounded-full border bg-emerald-100 text-emerald-700 border-emerald-200">Published</span>
|
|
12
|
+
<% else %>
|
|
13
|
+
<span class="inline-flex px-2 py-1 text-xs font-semibold rounded-full border bg-muted text-muted-foreground border-border/60">Draft</span>
|
|
14
|
+
<% end %>
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
<%= link_to ruby_cms_admin_content_block_path(@content_block),
|
|
17
|
+
data: { turbo_method: :delete, turbo_confirm: t("ruby_cms.admin.content_blocks.confirm_delete", default: "Are you sure?") },
|
|
18
|
+
class: "inline-flex items-center justify-center size-8 rounded-md text-muted-foreground hover:text-destructive hover:bg-destructive/10 transition-colors" do %>
|
|
19
|
+
<svg class="size-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
20
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
|
21
|
+
</svg>
|
|
22
|
+
<% end %>
|
|
23
|
+
<% end %>
|
|
14
24
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
25
|
+
<%= form_with model: @content_block,
|
|
26
|
+
url: ruby_cms_admin_content_block_path(@content_block),
|
|
27
|
+
method: :patch,
|
|
28
|
+
id: "content-block-form",
|
|
29
|
+
data: { turbo: false } do |f| %>
|
|
30
|
+
<div class="<%= RubyCms::Admin::AdminResourceCard::CARD_CLASS %>">
|
|
31
|
+
<div class="<%= RubyCms::Admin::AdminResourceCard::GRID_CLASS %>">
|
|
32
|
+
<%# ── Form Fields (Left 2/3) ── %>
|
|
33
|
+
<div class="<%= RubyCms::Admin::AdminResourceCard::MAIN_CLASS %>">
|
|
34
|
+
<% if @content_block.errors.any? %>
|
|
35
|
+
<div class="rounded-lg border border-destructive/30 bg-destructive/5 p-4">
|
|
36
|
+
<div class="flex gap-3">
|
|
37
|
+
<svg class="size-5 text-destructive shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
|
38
|
+
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
|
|
39
|
+
</svg>
|
|
40
|
+
<div>
|
|
41
|
+
<p class="text-sm font-medium text-destructive"><%= t("ruby_cms.admin.content_blocks.errors_title", default: "Please fix the following errors:") %></p>
|
|
42
|
+
<ul class="mt-1.5 text-sm text-destructive/80 list-disc list-inside">
|
|
43
|
+
<% @content_block.errors.full_messages.each do |message| %>
|
|
44
|
+
<li><%= message %></li>
|
|
45
|
+
<% end %>
|
|
46
|
+
</ul>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
<% end %>
|
|
20
51
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
52
|
+
<%# ── Key & Content Type ── %>
|
|
53
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
|
|
54
|
+
<div>
|
|
55
|
+
<label class="<%= RubyCms::Admin::AdminResourceCard::LABEL_CLASS %>"><%= t("ruby_cms.admin.content_blocks.key", default: "Key") %></label>
|
|
56
|
+
<span class="block text-sm font-mono text-foreground py-2"><%= @content_block.key %></span>
|
|
57
|
+
<%= f.hidden_field :key, name: nil, value: @content_block.key %>
|
|
58
|
+
</div>
|
|
25
59
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
60
|
+
<div>
|
|
61
|
+
<label class="<%= RubyCms::Admin::AdminResourceCard::LABEL_CLASS %>"><%= t("ruby_cms.admin.content_blocks.content_type", default: "Content Type") %></label>
|
|
62
|
+
<%= select_tag "content_block[content_type]",
|
|
63
|
+
options_for_select(::ContentBlock::CONTENT_TYPES, @content_block.content_type),
|
|
64
|
+
class: RubyCms::Admin::AdminResourceCard::INPUT_CLASS %>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
30
67
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
68
|
+
<%# ── Published checkbox ── %>
|
|
69
|
+
<% if @blocks_by_locale.present? %>
|
|
70
|
+
<label class="inline-flex items-center gap-3 cursor-pointer">
|
|
71
|
+
<%= hidden_field_tag "content_block[published]", "0" %>
|
|
72
|
+
<%= check_box_tag "content_block[published]", "1",
|
|
73
|
+
@blocks_by_locale.values.any?(&:published?),
|
|
74
|
+
class: "size-4 rounded border-border text-primary focus:ring-primary/30 cursor-pointer" %>
|
|
75
|
+
<span class="text-sm font-medium text-foreground"><%= t("ruby_cms.admin.content_blocks.published", default: "Published") %> (all translations)</span>
|
|
76
|
+
</label>
|
|
77
|
+
<% end %>
|
|
78
|
+
|
|
79
|
+
<%# ── Locale Tabs ── %>
|
|
80
|
+
<% if @blocks_by_locale.present? %>
|
|
81
|
+
<div class="rounded-xl border border-border/60 overflow-hidden" data-controller="ruby-cms--locale-tabs">
|
|
82
|
+
<div class="flex gap-1 p-2 bg-muted/30 border-b border-border/60" role="tablist">
|
|
83
|
+
<% @blocks_by_locale.each_with_index do |(locale_s, _), idx| %>
|
|
84
|
+
<button type="button"
|
|
85
|
+
role="tab"
|
|
86
|
+
aria-selected="<%= idx == 0 %>"
|
|
87
|
+
aria-controls="locale-panel-<%= locale_s %>"
|
|
88
|
+
id="locale-tab-<%= locale_s %>"
|
|
89
|
+
data-ruby-cms--locale-tabs-target="tab"
|
|
90
|
+
data-panel-id="locale-panel-<%= locale_s %>"
|
|
91
|
+
data-action="click->ruby-cms--locale-tabs#switchTab"
|
|
92
|
+
class="px-3 py-1.5 rounded-md text-xs font-medium border-none cursor-pointer transition-colors <%= idx == 0 ? 'bg-background text-primary shadow-sm ring-1 ring-border/60' : 'bg-transparent text-muted-foreground hover:bg-muted hover:text-foreground' %>">
|
|
93
|
+
<%= ruby_cms_locale_display_name(locale_s) %>
|
|
94
|
+
</button>
|
|
95
|
+
<% end %>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<% @blocks_by_locale.each_with_index do |(locale_s, block), idx| %>
|
|
99
|
+
<div id="locale-panel-<%= locale_s %>"
|
|
100
|
+
role="tabpanel"
|
|
101
|
+
aria-labelledby="locale-tab-<%= locale_s %>"
|
|
102
|
+
class="p-5 <%= idx > 0 ? 'hidden' : '' %>"
|
|
103
|
+
data-locale-panel>
|
|
104
|
+
|
|
105
|
+
<%= hidden_field_tag "content_block[locales][#{locale_s}][locale]", locale_s %>
|
|
106
|
+
|
|
107
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
108
|
+
<div>
|
|
109
|
+
<label class="<%= RubyCms::Admin::AdminResourceCard::LABEL_CLASS %>"><%= t("ruby_cms.admin.content_blocks.title", default: "Title") %></label>
|
|
110
|
+
<%= text_field_tag "content_block[locales][#{locale_s}][title]",
|
|
111
|
+
block.title,
|
|
112
|
+
class: RubyCms::Admin::AdminResourceCard::INPUT_CLASS %>
|
|
113
|
+
</div>
|
|
35
114
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
115
|
+
<div class="sm:col-span-2">
|
|
116
|
+
<label class="<%= RubyCms::Admin::AdminResourceCard::LABEL_CLASS %>"><%= t("ruby_cms.admin.content_blocks.content", default: "Content") %></label>
|
|
117
|
+
<%= text_area_tag "content_block[locales][#{locale_s}][content]",
|
|
118
|
+
block.content,
|
|
119
|
+
rows: 5,
|
|
120
|
+
class: "#{RubyCms::Admin::AdminResourceCard::INPUT_CLASS} resize-y" %>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<% if ::ContentBlock.respond_to?(:action_text_available?) && ::ContentBlock.action_text_available? && block.persisted? && block.respond_to?(:rich_content) %>
|
|
124
|
+
<div class="sm:col-span-2">
|
|
125
|
+
<label class="<%= RubyCms::Admin::AdminResourceCard::LABEL_CLASS %>"><%= t("ruby_cms.admin.content_blocks.rich_content", default: "Rich Content") %></label>
|
|
126
|
+
<%= rich_text_area_tag "content_block[locales][#{locale_s}][rich_content]",
|
|
127
|
+
block.rich_content.present? ? block.rich_content.to_s : "",
|
|
128
|
+
class: "rounded-lg border border-border bg-background text-sm shadow-sm focus:outline-none" %>
|
|
129
|
+
</div>
|
|
130
|
+
<% end %>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
<% end %>
|
|
134
|
+
</div>
|
|
43
135
|
<% end %>
|
|
44
136
|
</div>
|
|
45
|
-
</div>
|
|
46
137
|
|
|
47
|
-
|
|
48
|
-
<div class="
|
|
49
|
-
|
|
50
|
-
|
|
138
|
+
<%# ── Details Sidebar (Right 1/3) ── %>
|
|
139
|
+
<div class="<%= RubyCms::Admin::AdminResourceCard::SIDEBAR_CLASS %>">
|
|
140
|
+
<div>
|
|
141
|
+
<h3 class="<%= RubyCms::Admin::AdminResourceCard::SECTION_TITLE_CLASS %>">Details</h3>
|
|
142
|
+
<dl class="space-y-4">
|
|
143
|
+
<div>
|
|
144
|
+
<dt class="<%= RubyCms::Admin::AdminResourceCard::DETAIL_LABEL_CLASS %>">ID</dt>
|
|
145
|
+
<dd class="<%= RubyCms::Admin::AdminResourceCard::DETAIL_VALUE_CLASS %> font-mono"><%= @content_block.id %></dd>
|
|
146
|
+
</div>
|
|
51
147
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
148
|
+
<div>
|
|
149
|
+
<dt class="<%= RubyCms::Admin::AdminResourceCard::DETAIL_LABEL_CLASS %>">Key</dt>
|
|
150
|
+
<dd class="mt-1">
|
|
151
|
+
<code class="text-xs bg-muted px-2 py-1 rounded-md font-mono text-foreground"><%= @content_block.key %></code>
|
|
152
|
+
</dd>
|
|
153
|
+
</div>
|
|
56
154
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
</div>
|
|
62
|
-
<% end %>
|
|
63
|
-
</div>
|
|
155
|
+
<div>
|
|
156
|
+
<dt class="<%= RubyCms::Admin::AdminResourceCard::DETAIL_LABEL_CLASS %>">Locale</dt>
|
|
157
|
+
<dd class="<%= RubyCms::Admin::AdminResourceCard::DETAIL_VALUE_CLASS %>"><%= ruby_cms_locale_display_name(@content_block.locale) %></dd>
|
|
158
|
+
</div>
|
|
64
159
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
<div class="px-3 py-2 border-b border-gray-200 bg-gray-100">
|
|
70
|
-
<span class="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium bg-white text-gray-600 ring-1 ring-gray-200">
|
|
71
|
-
<%= ruby_cms_locale_display_name(locale_s) %>
|
|
72
|
-
</span>
|
|
73
|
-
</div>
|
|
160
|
+
<div>
|
|
161
|
+
<dt class="<%= RubyCms::Admin::AdminResourceCard::DETAIL_LABEL_CLASS %>">Type</dt>
|
|
162
|
+
<dd class="<%= RubyCms::Admin::AdminResourceCard::DETAIL_VALUE_CLASS %>"><%= @content_block.content_type %></dd>
|
|
163
|
+
</div>
|
|
74
164
|
|
|
75
|
-
<div class="p-4 grid grid-cols-2 gap-4">
|
|
76
165
|
<div>
|
|
77
|
-
<
|
|
78
|
-
<
|
|
166
|
+
<dt class="<%= RubyCms::Admin::AdminResourceCard::DETAIL_LABEL_CLASS %>">Status</dt>
|
|
167
|
+
<dd class="mt-1">
|
|
168
|
+
<% if @content_block.published? %>
|
|
169
|
+
<span class="inline-flex items-center rounded-md bg-emerald-50 px-2 py-0.5 text-xs font-medium text-emerald-700 ring-1 ring-inset ring-emerald-200">Published</span>
|
|
170
|
+
<% else %>
|
|
171
|
+
<span class="inline-flex items-center rounded-md bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground ring-1 ring-inset ring-border/60">Draft</span>
|
|
172
|
+
<% end %>
|
|
173
|
+
</dd>
|
|
79
174
|
</div>
|
|
175
|
+
</dl>
|
|
176
|
+
</div>
|
|
80
177
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
178
|
+
<div class="border-t border-border/60 pt-5">
|
|
179
|
+
<h3 class="<%= RubyCms::Admin::AdminResourceCard::SECTION_TITLE_CLASS %> mb-3">Timestamps</h3>
|
|
180
|
+
<dl class="space-y-4">
|
|
181
|
+
<div>
|
|
182
|
+
<dt class="<%= RubyCms::Admin::AdminResourceCard::DETAIL_LABEL_CLASS %>">Created</dt>
|
|
183
|
+
<dd class="<%= RubyCms::Admin::AdminResourceCard::DETAIL_VALUE_CLASS %>"><%= @content_block.created_at.strftime("%b %d, %Y") %></dd>
|
|
84
184
|
</div>
|
|
85
185
|
|
|
86
|
-
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
|
|
186
|
+
<div>
|
|
187
|
+
<dt class="<%= RubyCms::Admin::AdminResourceCard::DETAIL_LABEL_CLASS %>">Last Updated</dt>
|
|
188
|
+
<dd class="<%= RubyCms::Admin::AdminResourceCard::DETAIL_VALUE_CLASS %>"><%= @content_block.updated_at.strftime("%b %d, %Y") %></dd>
|
|
189
|
+
</div>
|
|
190
|
+
|
|
191
|
+
<% if @content_block.updated_by.present? %>
|
|
192
|
+
<div>
|
|
193
|
+
<dt class="<%= RubyCms::Admin::AdminResourceCard::DETAIL_LABEL_CLASS %>">Updated By</dt>
|
|
194
|
+
<dd class="<%= RubyCms::Admin::AdminResourceCard::DETAIL_VALUE_CLASS %>"><%= ruby_cms_user_display(@content_block.updated_by) %></dd>
|
|
90
195
|
</div>
|
|
91
196
|
<% end %>
|
|
92
|
-
</
|
|
197
|
+
</dl>
|
|
93
198
|
</div>
|
|
94
|
-
<% end %>
|
|
95
|
-
</div>
|
|
96
|
-
<% else %>
|
|
97
|
-
<div class="grid grid-cols-2 gap-6 p-6">
|
|
98
|
-
<div>
|
|
99
|
-
<div class="text-xs font-medium uppercase tracking-wide text-gray-400 mb-1">Title</div>
|
|
100
|
-
<div class="text-sm text-gray-900"><%= @content_block.title %></div>
|
|
101
|
-
</div>
|
|
102
199
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
200
|
+
<%# ── Preview per locale ── %>
|
|
201
|
+
<% if @blocks_by_locale.present? && @blocks_by_locale.values.any? { |b| b.title.present? || b.content_body.present? } %>
|
|
202
|
+
<div class="border-t border-border/60 pt-5">
|
|
203
|
+
<h3 class="<%= RubyCms::Admin::AdminResourceCard::SECTION_TITLE_CLASS %> mb-3">Content Preview</h3>
|
|
204
|
+
<div class="space-y-3">
|
|
205
|
+
<% @blocks_by_locale.each do |locale_s, block| %>
|
|
206
|
+
<% next if block.title.blank? && block.content_body.blank? %>
|
|
207
|
+
<div class="rounded-lg border border-border/60 bg-muted/30 p-3">
|
|
208
|
+
<span class="inline-flex items-center rounded-md bg-background px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground ring-1 ring-border/60 mb-2"><%= ruby_cms_locale_display_name(locale_s) %></span>
|
|
209
|
+
<% if block.title.present? %>
|
|
210
|
+
<p class="text-xs font-medium text-foreground"><%= block.title %></p>
|
|
211
|
+
<% end %>
|
|
212
|
+
<% if block.content_body.present? %>
|
|
213
|
+
<p class="text-xs text-muted-foreground mt-1 line-clamp-3"><%= block.content_body %></p>
|
|
214
|
+
<% end %>
|
|
215
|
+
</div>
|
|
216
|
+
<% end %>
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
<% end %>
|
|
106
220
|
</div>
|
|
107
221
|
</div>
|
|
108
|
-
<% end %>
|
|
109
222
|
|
|
110
|
-
|
|
223
|
+
<%# ── Actions Footer ── %>
|
|
224
|
+
<div class="<%= RubyCms::Admin::AdminResourceCard::ACTIONS_CLASS %>">
|
|
225
|
+
<%= link_to t("ruby_cms.admin.common.cancel", default: "Cancel"), ruby_cms_admin_content_blocks_path, class: RubyCms::Admin::AdminResourceCard::CANCEL_CLASS %>
|
|
226
|
+
<%= f.submit t("ruby_cms.admin.common.save", default: "Save Changes"), class: RubyCms::Admin::AdminResourceCard::SUBMIT_CLASS %>
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
<% end %>
|