lean_cms 0.2.12

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 (130) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +235 -0
  3. data/LICENSE +21 -0
  4. data/README.md +107 -0
  5. data/app/assets/images/lean_cms/sloth-404.png +0 -0
  6. data/app/assets/images/lean_cms/sloth-500.png +0 -0
  7. data/app/assets/images/lean_cms/sloth-favicon-16.png +0 -0
  8. data/app/assets/images/lean_cms/sloth-favicon-32.png +0 -0
  9. data/app/assets/images/lean_cms/sloth-favicon-64.png +0 -0
  10. data/app/assets/images/lean_cms/sloth-logo.png +0 -0
  11. data/app/assets/lean_cms/actiontext.css +440 -0
  12. data/app/assets/lean_cms/cms_edit_controls.css +548 -0
  13. data/app/assets/tailwind/lean_cms/engine.css +14 -0
  14. data/app/components/lean_cms/base_component.rb +61 -0
  15. data/app/components/lean_cms/bullets_section_component.html.erb +23 -0
  16. data/app/components/lean_cms/bullets_section_component.rb +54 -0
  17. data/app/components/lean_cms/cards_section_component.html.erb +237 -0
  18. data/app/components/lean_cms/cards_section_component.rb +71 -0
  19. data/app/components/lean_cms/editable_content_component.html.erb +15 -0
  20. data/app/components/lean_cms/editable_content_component.rb +53 -0
  21. data/app/components/lean_cms/section_component.html.erb +18 -0
  22. data/app/components/lean_cms/section_component.rb +35 -0
  23. data/app/controllers/concerns/lean_cms/authentication.rb +60 -0
  24. data/app/controllers/concerns/lean_cms/authorization.rb +60 -0
  25. data/app/controllers/lean_cms/activity_controller.rb +16 -0
  26. data/app/controllers/lean_cms/application_controller.rb +48 -0
  27. data/app/controllers/lean_cms/dashboard_controller.rb +13 -0
  28. data/app/controllers/lean_cms/form_submissions_controller.rb +37 -0
  29. data/app/controllers/lean_cms/notification_settings_controller.rb +145 -0
  30. data/app/controllers/lean_cms/notifications_controller.rb +26 -0
  31. data/app/controllers/lean_cms/page_contents_controller.rb +403 -0
  32. data/app/controllers/lean_cms/password_setup_controller.rb +65 -0
  33. data/app/controllers/lean_cms/passwords_controller.rb +42 -0
  34. data/app/controllers/lean_cms/posts_controller.rb +78 -0
  35. data/app/controllers/lean_cms/sessions_controller.rb +50 -0
  36. data/app/controllers/lean_cms/settings_controller.rb +124 -0
  37. data/app/controllers/lean_cms/users_controller.rb +113 -0
  38. data/app/helpers/lean_cms/activity_helper.rb +190 -0
  39. data/app/helpers/lean_cms/application_helper.rb +43 -0
  40. data/app/helpers/lean_cms/content_helper.rb +34 -0
  41. data/app/helpers/lean_cms/page_content_helper.rb +359 -0
  42. data/app/javascript/controllers/cards_editor_controller.js +317 -0
  43. data/app/javascript/controllers/cms_sticky_overlay_controller.js +59 -0
  44. data/app/javascript/controllers/field_editor_form_controller.js +68 -0
  45. data/app/javascript/controllers/field_editor_modal_controller.js +79 -0
  46. data/app/javascript/controllers/inline_edit_controller.js +414 -0
  47. data/app/javascript/controllers/inline_edit_toggle_controller.js +81 -0
  48. data/app/javascript/controllers/notifications_controller.js +19 -0
  49. data/app/javascript/controllers/settings_inline_edit_sync_controller.js +38 -0
  50. data/app/javascript/controllers/settings_override_controller.js +45 -0
  51. data/app/mailers/lean_cms/application_mailer.rb +6 -0
  52. data/app/mailers/lean_cms/passwords_mailer.rb +8 -0
  53. data/app/mailers/lean_cms/users_mailer.rb +39 -0
  54. data/app/models/lean_cms/current.rb +6 -0
  55. data/app/models/lean_cms/form_submission.rb +45 -0
  56. data/app/models/lean_cms/magic_link.rb +76 -0
  57. data/app/models/lean_cms/meta_tag.rb +30 -0
  58. data/app/models/lean_cms/notification_setting.rb +69 -0
  59. data/app/models/lean_cms/page.rb +23 -0
  60. data/app/models/lean_cms/page_content.rb +245 -0
  61. data/app/models/lean_cms/post.rb +65 -0
  62. data/app/models/lean_cms/session.rb +7 -0
  63. data/app/models/lean_cms/setting.rb +156 -0
  64. data/app/policies/lean_cms/application_policy.rb +35 -0
  65. data/app/policies/lean_cms/page_content_policy.rb +31 -0
  66. data/app/policies/lean_cms/post_policy.rb +37 -0
  67. data/app/policies/lean_cms/setting_policy.rb +17 -0
  68. data/app/views/layouts/lean_cms/application.html.erb +114 -0
  69. data/app/views/layouts/lean_cms/auth.html.erb +200 -0
  70. data/app/views/lean_cms/activity/index.html.erb +79 -0
  71. data/app/views/lean_cms/dashboard/index.html.erb +180 -0
  72. data/app/views/lean_cms/form_submissions/index.html.erb +104 -0
  73. data/app/views/lean_cms/form_submissions/show.html.erb +157 -0
  74. data/app/views/lean_cms/notification_settings/edit.html.erb +192 -0
  75. data/app/views/lean_cms/notifications/index.html.erb +72 -0
  76. data/app/views/lean_cms/notifications/show.html.erb +39 -0
  77. data/app/views/lean_cms/page_contents/_field_editor.html.erb +174 -0
  78. data/app/views/lean_cms/page_contents/edit.html.erb +428 -0
  79. data/app/views/lean_cms/page_contents/index.html.erb +113 -0
  80. data/app/views/lean_cms/password_setup/show.html.erb +35 -0
  81. data/app/views/lean_cms/passwords/edit.html.erb +26 -0
  82. data/app/views/lean_cms/passwords/new.html.erb +21 -0
  83. data/app/views/lean_cms/passwords_mailer/reset.html.erb +6 -0
  84. data/app/views/lean_cms/passwords_mailer/reset.text.erb +4 -0
  85. data/app/views/lean_cms/posts/_form.html.erb +118 -0
  86. data/app/views/lean_cms/posts/edit.html.erb +31 -0
  87. data/app/views/lean_cms/posts/index.html.erb +100 -0
  88. data/app/views/lean_cms/posts/new.html.erb +16 -0
  89. data/app/views/lean_cms/sessions/new.html.erb +28 -0
  90. data/app/views/lean_cms/settings/edit.html.erb +384 -0
  91. data/app/views/lean_cms/shared/_admin_bar.html.erb +85 -0
  92. data/app/views/lean_cms/shared/_header.html.erb +86 -0
  93. data/app/views/lean_cms/shared/_notifications_bell.html.erb +84 -0
  94. data/app/views/lean_cms/shared/_sidebar.html.erb +102 -0
  95. data/app/views/lean_cms/users/_form.html.erb +105 -0
  96. data/app/views/lean_cms/users/edit.html.erb +8 -0
  97. data/app/views/lean_cms/users/index.html.erb +99 -0
  98. data/app/views/lean_cms/users/new.html.erb +8 -0
  99. data/app/views/lean_cms/users_mailer/admin_triggered_password_reset.html.erb +13 -0
  100. data/app/views/lean_cms/users_mailer/admin_triggered_password_reset.text.erb +11 -0
  101. data/app/views/lean_cms/users_mailer/invitation.html.erb +13 -0
  102. data/app/views/lean_cms/users_mailer/invitation.text.erb +11 -0
  103. data/app/views/lean_cms/users_mailer/reactivation.html.erb +13 -0
  104. data/app/views/lean_cms/users_mailer/reactivation.text.erb +11 -0
  105. data/config/importmap.rb +8 -0
  106. data/config/routes.rb +78 -0
  107. data/db/migrate/20251112034030_create_lean_cms_tables.rb +131 -0
  108. data/db/migrate/20260513000001_create_lean_cms_auth_tables.rb +31 -0
  109. data/db/migrate/20260514000001_create_paper_trail_versions.rb +16 -0
  110. data/db/migrate/20260514000002_create_action_text_tables.rb +18 -0
  111. data/db/migrate/20260514000003_create_active_storage_tables.rb +45 -0
  112. data/db/migrate/20260514000004_create_noticed_tables.rb +27 -0
  113. data/lib/generators/lean_cms/demo/demo_generator.rb +54 -0
  114. data/lib/generators/lean_cms/demo/templates/lean_cms_structure.yml +129 -0
  115. data/lib/generators/lean_cms/demo/templates/pages_controller.rb +30 -0
  116. data/lib/generators/lean_cms/demo/templates/views/pages/about.html.erb +40 -0
  117. data/lib/generators/lean_cms/demo/templates/views/pages/contact.html.erb +55 -0
  118. data/lib/generators/lean_cms/demo/templates/views/pages/home.html.erb +31 -0
  119. data/lib/generators/lean_cms/install/install_generator.rb +317 -0
  120. data/lib/generators/lean_cms/install/templates/add_lean_cms_columns_to_users.rb.tt +7 -0
  121. data/lib/generators/lean_cms/install/templates/lean_cms.rb +11 -0
  122. data/lib/generators/lean_cms/install/templates/lean_cms_structure.yml +29 -0
  123. data/lib/lean_cms/configuration.rb +32 -0
  124. data/lib/lean_cms/engine.rb +93 -0
  125. data/lib/lean_cms/loader.rb +217 -0
  126. data/lib/lean_cms/sync_helper.rb +182 -0
  127. data/lib/lean_cms/version.rb +3 -0
  128. data/lib/lean_cms.rb +26 -0
  129. data/lib/tasks/lean_cms.rake +390 -0
  130. metadata +313 -0
@@ -0,0 +1,79 @@
1
+ <% content_for :page_title, "Activity Log" %>
2
+
3
+ <div class="max-w-6xl">
4
+ <div class="mb-8">
5
+ <h1 class="text-3xl font-bold text-gray-900">Activity Log</h1>
6
+ <p class="text-gray-600 mt-2">Track who changed what across the CMS</p>
7
+ </div>
8
+
9
+ <% if params[:item_type].present? || params[:whodunnit].present? %>
10
+ <div class="mb-4 flex items-center gap-2">
11
+ <%= link_to lean_cms_activity_path, class: "text-sm text-[#b82025] hover:underline" do %>
12
+ Clear filters
13
+ <% end %>
14
+ <% if params[:item_type].present? %>
15
+ <span class="text-sm text-gray-500">Filtering by: <%= params[:item_type] %></span>
16
+ <% end %>
17
+ <% if params[:whodunnit].present? %>
18
+ <span class="text-sm text-gray-500">User: <%= User.unscoped.find_by(id: params[:whodunnit])&.email_address || "##{params[:whodunnit]}" %></span>
19
+ <% end %>
20
+ </div>
21
+ <% end %>
22
+
23
+ <div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
24
+ <% if @versions.any? %>
25
+ <div class="overflow-x-auto">
26
+ <table class="min-w-full divide-y divide-gray-200">
27
+ <thead class="bg-gray-50">
28
+ <tr>
29
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">When</th>
30
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Who</th>
31
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">What</th>
32
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Action</th>
33
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Old value</th>
34
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">New value</th>
35
+ </tr>
36
+ </thead>
37
+ <tbody class="bg-white divide-y divide-gray-200">
38
+ <% @versions.each do |version| %>
39
+ <tr class="hover:bg-gray-50">
40
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
41
+ <%= version.created_at ? format_datetime(version.created_at) : "—" %>
42
+ </td>
43
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
44
+ <%= activity_who(version) %>
45
+ </td>
46
+ <td class="px-6 py-4 text-sm text-gray-900">
47
+ <%= activity_item_label(version) %>
48
+ </td>
49
+ <td class="px-6 py-4 whitespace-nowrap">
50
+ <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium <%= activity_action_badge_class(version.event) %>">
51
+ <%= version.event %>
52
+ </span>
53
+ </td>
54
+ <td class="px-6 py-4 text-sm text-gray-600 max-w-xs truncate" title="<%= activity_old_value(version) %>">
55
+ <%= activity_old_value(version) %>
56
+ </td>
57
+ <td class="px-6 py-4 text-sm text-gray-600 max-w-xs truncate" title="<%= activity_new_value(version) %>">
58
+ <%= activity_new_value(version) %>
59
+ </td>
60
+ </tr>
61
+ <% end %>
62
+ </tbody>
63
+ </table>
64
+ </div>
65
+
66
+ <div class="px-6 py-4 border-t border-gray-200">
67
+ <%= paginate @versions %>
68
+ </div>
69
+ <% else %>
70
+ <div class="px-6 py-12 text-center">
71
+ <svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
72
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01"/>
73
+ </svg>
74
+ <p class="mt-2 text-sm text-gray-600">No activity yet</p>
75
+ <p class="mt-1 text-xs text-gray-500">Changes to page content, settings, posts, and users will appear here</p>
76
+ </div>
77
+ <% end %>
78
+ </div>
79
+ </div>
@@ -0,0 +1,180 @@
1
+ <% content_for :page_title, "Dashboard" %>
2
+
3
+ <!-- Stats Grid -->
4
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
5
+ <!-- Published Posts -->
6
+ <div class="bg-white rounded-lg border border-gray-200 p-6">
7
+ <div class="flex items-center justify-between mb-2">
8
+ <h3 class="text-sm font-medium text-gray-600">Published Posts</h3>
9
+ <div class="h-10 w-10 rounded-lg flex items-center justify-center" style="background-color: rgba(37, 99, 235, 0.1);">
10
+ <svg class="w-5 h-5" style="color: var(--cms-secondary);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
11
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
12
+ </svg>
13
+ </div>
14
+ </div>
15
+ <p class="text-3xl font-bold text-gray-900"><%= @published_posts_count %></p>
16
+ </div>
17
+
18
+ <!-- Draft Posts -->
19
+ <div class="bg-white rounded-lg border border-gray-200 p-6">
20
+ <div class="flex items-center justify-between mb-2">
21
+ <h3 class="text-sm font-medium text-gray-600">Draft Posts</h3>
22
+ <div class="h-10 w-10 rounded-lg flex items-center justify-center bg-yellow-50">
23
+ <svg class="w-5 h-5 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
24
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
25
+ </svg>
26
+ </div>
27
+ </div>
28
+ <p class="text-3xl font-bold text-gray-900"><%= @draft_posts_count %></p>
29
+ </div>
30
+
31
+ <!-- Unread Submissions -->
32
+ <div class="bg-white rounded-lg border border-gray-200 p-6">
33
+ <div class="flex items-center justify-between mb-2">
34
+ <h3 class="text-sm font-medium text-gray-600">Unread Messages</h3>
35
+ <div class="h-10 w-10 rounded-lg flex items-center justify-center" style="background-color: rgba(184, 32, 37, 0.1);">
36
+ <svg class="w-5 h-5" style="color: var(--cms-primary);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
37
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
38
+ </svg>
39
+ </div>
40
+ </div>
41
+ <p class="text-3xl font-bold text-gray-900"><%= @unread_submissions_count %></p>
42
+ </div>
43
+
44
+ <!-- Total Page Sections -->
45
+ <div class="bg-white rounded-lg border border-gray-200 p-6">
46
+ <div class="flex items-center justify-between mb-2">
47
+ <h3 class="text-sm font-medium text-gray-600">Page Sections</h3>
48
+ <div class="h-10 w-10 rounded-lg flex items-center justify-center bg-green-50">
49
+ <svg class="w-5 h-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
50
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z"/>
51
+ </svg>
52
+ </div>
53
+ </div>
54
+ <p class="text-3xl font-bold text-gray-900"><%= LeanCms::PageContent.count %></p>
55
+ </div>
56
+ </div>
57
+
58
+ <!-- Recent Activity Grid -->
59
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
60
+ <!-- Recent Posts -->
61
+ <div class="bg-white rounded-lg border border-gray-200">
62
+ <div class="px-6 py-4 border-b border-gray-200">
63
+ <div class="flex items-center justify-between">
64
+ <h2 class="text-lg font-semibold text-gray-900">Recent Posts</h2>
65
+ <%= link_to "View All", lean_cms_posts_path, class: "text-sm font-medium hover:underline", style: "color: var(--cms-primary);" %>
66
+ </div>
67
+ </div>
68
+ <div class="divide-y divide-gray-200">
69
+ <% if @recent_posts.any? %>
70
+ <% @recent_posts.each do |post| %>
71
+ <div class="px-6 py-4 hover:bg-gray-50 transition-colors">
72
+ <%= link_to edit_lean_cms_post_path(post), class: "block" do %>
73
+ <div class="flex items-start justify-between">
74
+ <div class="flex-1 min-w-0">
75
+ <p class="text-sm font-medium text-gray-900 truncate"><%= post.title %></p>
76
+ <p class="text-xs text-gray-500 mt-1">
77
+ <%= format_datetime(post.created_at) %> by <%= post.author.email_address.split('@').first %>
78
+ </p>
79
+ </div>
80
+ <span class="ml-4 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium <%= status_badge_class(post.status) %>">
81
+ <%= post.status.titleize %>
82
+ </span>
83
+ </div>
84
+ <% end %>
85
+ </div>
86
+ <% end %>
87
+ <% else %>
88
+ <div class="px-6 py-12 text-center">
89
+ <svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
90
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
91
+ </svg>
92
+ <p class="mt-2 text-sm text-gray-600">No posts yet</p>
93
+ <%= link_to "Create your first post", new_lean_cms_post_path, class: "mt-3 inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-lg text-white transition-colors hover:opacity-90", style: "background-color: var(--cms-primary);" %>
94
+ </div>
95
+ <% end %>
96
+ </div>
97
+ </div>
98
+
99
+ <!-- Recent Form Submissions -->
100
+ <div class="bg-white rounded-lg border border-gray-200">
101
+ <div class="px-6 py-4 border-b border-gray-200">
102
+ <div class="flex items-center justify-between">
103
+ <h2 class="text-lg font-semibold text-gray-900">Recent Submissions</h2>
104
+ <%= link_to "View All", lean_cms_form_submissions_path, class: "text-sm font-medium hover:underline", style: "color: var(--cms-primary);" %>
105
+ </div>
106
+ </div>
107
+ <div class="divide-y divide-gray-200">
108
+ <% if @recent_submissions.any? %>
109
+ <% @recent_submissions.each do |submission| %>
110
+ <div class="px-6 py-4 hover:bg-gray-50 transition-colors">
111
+ <%= link_to lean_cms_form_submission_path(submission), class: "block" do %>
112
+ <div class="flex items-start justify-between">
113
+ <div class="flex-1 min-w-0">
114
+ <p class="text-sm font-medium text-gray-900 truncate">
115
+ <%= submission.name || submission.email %>
116
+ </p>
117
+ <p class="text-xs text-gray-500 mt-1">
118
+ <%= submission.form_type.titleize %> - <%= format_datetime(submission.created_at) %>
119
+ </p>
120
+ </div>
121
+ <span class="ml-4 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium <%= status_badge_class(submission.status) %>">
122
+ <%= submission.status.titleize.gsub('_', ' ') %>
123
+ </span>
124
+ </div>
125
+ <% end %>
126
+ </div>
127
+ <% end %>
128
+ <% else %>
129
+ <div class="px-6 py-12 text-center">
130
+ <svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
131
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
132
+ </svg>
133
+ <p class="mt-2 text-sm text-gray-600">No submissions yet</p>
134
+ </div>
135
+ <% end %>
136
+ </div>
137
+ </div>
138
+ </div>
139
+
140
+ <!-- Quick Actions -->
141
+ <div class="mt-6 bg-gradient-to-r from-blue-50 to-indigo-50 rounded-lg border border-blue-100 p-6">
142
+ <h3 class="text-lg font-semibold text-gray-900 mb-4">Quick Actions</h3>
143
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
144
+ <%= link_to new_lean_cms_post_path, class: "flex items-center p-4 bg-white rounded-lg border border-gray-200 hover:border-blue-300 hover:shadow-md transition-all" do %>
145
+ <div class="h-10 w-10 rounded-lg flex items-center justify-center mr-4" style="background-color: var(--cms-primary);">
146
+ <svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
147
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
148
+ </svg>
149
+ </div>
150
+ <div>
151
+ <p class="font-medium text-gray-900">Create Post</p>
152
+ <p class="text-xs text-gray-500">New blog post or portfolio item</p>
153
+ </div>
154
+ <% end %>
155
+
156
+ <%= link_to lean_cms_page_contents_path, class: "flex items-center p-4 bg-white rounded-lg border border-gray-200 hover:border-blue-300 hover:shadow-md transition-all" do %>
157
+ <div class="h-10 w-10 rounded-lg flex items-center justify-center bg-blue-100 mr-4">
158
+ <svg class="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
159
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
160
+ </svg>
161
+ </div>
162
+ <div>
163
+ <p class="font-medium text-gray-900">Edit Pages</p>
164
+ <p class="text-xs text-gray-500">Update page content</p>
165
+ </div>
166
+ <% end %>
167
+
168
+ <%= link_to root_path, target: "_blank", class: "flex items-center p-4 bg-white rounded-lg border border-gray-200 hover:border-blue-300 hover:shadow-md transition-all" do %>
169
+ <div class="h-10 w-10 rounded-lg flex items-center justify-center bg-green-100 mr-4">
170
+ <svg class="w-5 h-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
171
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
172
+ </svg>
173
+ </div>
174
+ <div>
175
+ <p class="font-medium text-gray-900">View Site</p>
176
+ <p class="text-xs text-gray-500">See your live website</p>
177
+ </div>
178
+ <% end %>
179
+ </div>
180
+ </div>
@@ -0,0 +1,104 @@
1
+ <% content_for :title, "Form Submissions" %>
2
+
3
+ <div class="mb-8">
4
+ <h1 class="text-3xl font-bold text-gray-900">Form Submissions</h1>
5
+ <p class="text-gray-600 mt-2">View and manage form submissions from your website</p>
6
+ </div>
7
+
8
+ <!-- Filters -->
9
+ <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4 mb-6">
10
+ <div class="flex items-center space-x-4">
11
+ <div class="flex items-center space-x-2">
12
+ <span class="text-sm font-medium text-gray-700">Filter by:</span>
13
+ </div>
14
+
15
+ <div class="flex items-center space-x-2">
16
+ <%= link_to lean_cms_form_submissions_path, class: "px-3 py-1.5 text-sm rounded-lg #{params[:status].blank? ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'} transition-colors" do %>
17
+ All
18
+ <% end %>
19
+
20
+ <% LeanCms::FormSubmission.statuses.keys.each do |status_key| %>
21
+ <%= link_to lean_cms_form_submissions_path(status: status_key), class: "px-3 py-1.5 text-sm rounded-lg #{params[:status] == status_key ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'} transition-colors capitalize" do %>
22
+ <%= status_key.gsub('_', ' ') %>
23
+ <% end %>
24
+ <% end %>
25
+ </div>
26
+ </div>
27
+ </div>
28
+
29
+ <% if @form_submissions.any? %>
30
+ <div class="bg-white rounded-lg shadow-sm border border-gray-200">
31
+ <div class="overflow-x-auto">
32
+ <table class="w-full">
33
+ <thead>
34
+ <tr class="border-b border-gray-200 bg-gray-50">
35
+ <th class="text-left px-6 py-3 text-xs font-semibold text-gray-600 uppercase tracking-wider">Status</th>
36
+ <th class="text-left px-6 py-3 text-xs font-semibold text-gray-600 uppercase tracking-wider">From</th>
37
+ <th class="text-left px-6 py-3 text-xs font-semibold text-gray-600 uppercase tracking-wider">Form Type</th>
38
+ <th class="text-left px-6 py-3 text-xs font-semibold text-gray-600 uppercase tracking-wider">Message</th>
39
+ <th class="text-left px-6 py-3 text-xs font-semibold text-gray-600 uppercase tracking-wider">Submitted</th>
40
+ <th class="text-right px-6 py-3 text-xs font-semibold text-gray-600 uppercase tracking-wider">Actions</th>
41
+ </tr>
42
+ </thead>
43
+ <tbody class="divide-y divide-gray-200">
44
+ <% @form_submissions.each do |submission| %>
45
+ <tr class="hover:bg-gray-50 transition-colors <%= 'bg-blue-50' if submission.new_submission? %>">
46
+ <td class="px-6 py-4">
47
+ <% case submission.status %>
48
+ <% when 'new_submission' %>
49
+ <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
50
+ <svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
51
+ <circle cx="10" cy="10" r="3"/>
52
+ </svg>
53
+ New
54
+ </span>
55
+ <% when 'read' %>
56
+ <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800">
57
+ Read
58
+ </span>
59
+ <% when 'replied' %>
60
+ <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
61
+ Replied
62
+ </span>
63
+ <% when 'archived' %>
64
+ <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">
65
+ Archived
66
+ </span>
67
+ <% end %>
68
+ </td>
69
+ <td class="px-6 py-4">
70
+ <div class="font-medium text-gray-900"><%= submission.name || "—" %></div>
71
+ <div class="text-sm text-gray-500"><%= submission.email %></div>
72
+ <% if submission.phone.present? %>
73
+ <div class="text-sm text-gray-500"><%= submission.phone %></div>
74
+ <% end %>
75
+ </td>
76
+ <td class="px-6 py-4">
77
+ <span class="text-sm text-gray-900 capitalize">
78
+ <%= submission.form_type.gsub('_', ' ') %>
79
+ </span>
80
+ </td>
81
+ <td class="px-6 py-4 text-sm text-gray-600">
82
+ <%= truncate(submission.message, length: 80) if submission.message.present? %>
83
+ </td>
84
+ <td class="px-6 py-4 text-sm text-gray-600">
85
+ <%= time_ago_in_words(submission.created_at) %> ago
86
+ </td>
87
+ <td class="px-6 py-4 text-right">
88
+ <%= link_to "View", lean_cms_form_submission_path(submission), class: "inline-flex items-center px-3 py-1.5 border border-gray-300 rounded-lg text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 transition-colors" %>
89
+ </td>
90
+ </tr>
91
+ <% end %>
92
+ </tbody>
93
+ </table>
94
+ </div>
95
+ </div>
96
+ <% else %>
97
+ <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-12 text-center">
98
+ <svg class="w-16 h-16 mx-auto text-gray-400 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
99
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
100
+ </svg>
101
+ <h3 class="text-lg font-medium text-gray-900 mb-2">No form submissions yet</h3>
102
+ <p class="text-gray-600">Form submissions will appear here when visitors fill out forms on your website.</p>
103
+ </div>
104
+ <% end %>
@@ -0,0 +1,157 @@
1
+ <% content_for :title, "Form Submission" %>
2
+
3
+ <div class="mb-8">
4
+ <div class="flex items-center text-sm text-gray-600 mb-4">
5
+ <%= link_to "Form Submissions", lean_cms_form_submissions_path, class: "hover:text-gray-900" %>
6
+ <svg class="w-4 h-4 mx-2" fill="currentColor" viewBox="0 0 20 20">
7
+ <path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
8
+ </svg>
9
+ <span class="text-gray-900">Submission Details</span>
10
+ </div>
11
+
12
+ <div class="flex items-center justify-between">
13
+ <div>
14
+ <h1 class="text-3xl font-bold text-gray-900 capitalize">
15
+ <%= @form_submission.form_type.gsub('_', ' ') %>
16
+ </h1>
17
+ <p class="text-gray-600 mt-2">
18
+ Submitted <%= time_ago_in_words(@form_submission.created_at) %> ago
19
+ </p>
20
+ </div>
21
+
22
+ <div class="flex items-center space-x-3">
23
+ <% if @form_submission.new_submission? %>
24
+ <%= button_to mark_as_read_lean_cms_form_submission_path(@form_submission), method: :patch, class: "inline-flex items-center px-3 py-2 border border-gray-300 rounded-lg text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 transition-colors" do %>
25
+ <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
26
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
27
+ </svg>
28
+ Mark as Read
29
+ <% end %>
30
+ <% end %>
31
+
32
+ <% if @form_submission.read? || @form_submission.new_submission? %>
33
+ <%= button_to mark_as_replied_lean_cms_form_submission_path(@form_submission), method: :patch, class: "inline-flex items-center px-3 py-2 border border-gray-300 rounded-lg text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 transition-colors" do %>
34
+ <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
35
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 19v-8.93a2 2 0 01.89-1.664l7-4.666a2 2 0 012.22 0l7 4.666A2 2 0 0121 10.07V19M3 19a2 2 0 002 2h14a2 2 0 002-2M3 19l6.75-4.5M21 19l-6.75-4.5M3 10l6.75 4.5M21 10l-6.75 4.5m0 0l-1.14.76a2 2 0 01-2.22 0l-1.14-.76"/>
36
+ </svg>
37
+ Mark as Replied
38
+ <% end %>
39
+ <% end %>
40
+
41
+ <%= button_to lean_cms_form_submission_path(@form_submission), method: :delete, data: { turbo_confirm: "Are you sure you want to delete this submission?" }, class: "inline-flex items-center px-3 py-2 border border-red-300 rounded-lg text-sm font-medium text-red-700 bg-white hover:bg-red-50 transition-colors" do %>
42
+ <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
43
+ <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"/>
44
+ </svg>
45
+ Delete
46
+ <% end %>
47
+ </div>
48
+ </div>
49
+ </div>
50
+
51
+ <!-- Status Badge -->
52
+ <div class="mb-6">
53
+ <% case @form_submission.status %>
54
+ <% when 'new_submission' %>
55
+ <span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800">
56
+ <svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
57
+ <circle cx="10" cy="10" r="3"/>
58
+ </svg>
59
+ New Submission
60
+ </span>
61
+ <% when 'read' %>
62
+ <span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-yellow-100 text-yellow-800">
63
+ <svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
64
+ <path d="M10 12a2 2 0 100-4 2 2 0 000 4z"/>
65
+ <path fill-rule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clip-rule="evenodd"/>
66
+ </svg>
67
+ Read
68
+ </span>
69
+ <% when 'replied' %>
70
+ <span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-green-100 text-green-800">
71
+ <svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
72
+ <path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z"/>
73
+ <path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z"/>
74
+ </svg>
75
+ Replied
76
+ </span>
77
+ <% when 'archived' %>
78
+ <span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-gray-100 text-gray-800">
79
+ Archived
80
+ </span>
81
+ <% end %>
82
+ </div>
83
+
84
+ <!-- Contact Information -->
85
+ <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6 mb-6">
86
+ <h2 class="text-lg font-semibold text-gray-900 mb-6">Contact Information</h2>
87
+
88
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
89
+ <% if @form_submission.name.present? %>
90
+ <div>
91
+ <label class="block text-sm font-medium text-gray-500 mb-1">Name</label>
92
+ <div class="text-gray-900"><%= @form_submission.name %></div>
93
+ </div>
94
+ <% end %>
95
+
96
+ <% if @form_submission.email.present? %>
97
+ <div>
98
+ <label class="block text-sm font-medium text-gray-500 mb-1">Email</label>
99
+ <div class="text-gray-900">
100
+ <a href="mailto:<%= @form_submission.email %>" class="text-blue-600 hover:text-blue-800 hover:underline">
101
+ <%= @form_submission.email %>
102
+ </a>
103
+ </div>
104
+ </div>
105
+ <% end %>
106
+
107
+ <% if @form_submission.phone.present? %>
108
+ <div>
109
+ <label class="block text-sm font-medium text-gray-500 mb-1">Phone</label>
110
+ <div class="text-gray-900">
111
+ <a href="tel:<%= @form_submission.phone %>" class="text-blue-600 hover:text-blue-800 hover:underline">
112
+ <%= @form_submission.phone %>
113
+ </a>
114
+ </div>
115
+ </div>
116
+ <% end %>
117
+
118
+ <% if @form_submission.company_name.present? %>
119
+ <div>
120
+ <label class="block text-sm font-medium text-gray-500 mb-1">Company</label>
121
+ <div class="text-gray-900"><%= @form_submission.company_name %></div>
122
+ </div>
123
+ <% end %>
124
+
125
+ <% if @form_submission.city.present? || @form_submission.state.present? || @form_submission.zip.present? %>
126
+ <div>
127
+ <label class="block text-sm font-medium text-gray-500 mb-1">Location</label>
128
+ <div class="text-gray-900">
129
+ <%= [@form_submission.city, @form_submission.state, @form_submission.zip].compact.join(', ') %>
130
+ </div>
131
+ </div>
132
+ <% end %>
133
+ </div>
134
+ </div>
135
+
136
+ <!-- Message -->
137
+ <% if @form_submission.message.present? %>
138
+ <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6 mb-6">
139
+ <h2 class="text-lg font-semibold text-gray-900 mb-4">Message</h2>
140
+ <div class="text-gray-900 whitespace-pre-wrap"><%= @form_submission.message %></div>
141
+ </div>
142
+ <% end %>
143
+
144
+ <!-- Additional Data -->
145
+ <% if @form_submission.additional_data.present? && @form_submission.additional_data.any? %>
146
+ <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
147
+ <h2 class="text-lg font-semibold text-gray-900 mb-6">Additional Information</h2>
148
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
149
+ <% @form_submission.additional_data.each do |key, value| %>
150
+ <div>
151
+ <label class="block text-sm font-medium text-gray-500 mb-1 capitalize"><%= key.to_s.gsub('_', ' ') %></label>
152
+ <div class="text-gray-900"><%= value %></div>
153
+ </div>
154
+ <% end %>
155
+ </div>
156
+ </div>
157
+ <% end %>