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,31 @@
1
+ class CreateLeanCmsAuthTables < ActiveRecord::Migration[8.1]
2
+ def change
3
+ unless table_exists?(:lean_cms_sessions)
4
+ create_table :lean_cms_sessions do |t|
5
+ t.references :user, null: false
6
+ t.string :ip_address
7
+ t.string :user_agent
8
+
9
+ t.timestamps
10
+ end
11
+ end
12
+
13
+ unless table_exists?(:lean_cms_magic_links)
14
+ create_table :lean_cms_magic_links do |t|
15
+ t.references :user, null: false
16
+ t.string :token, null: false
17
+ t.string :purpose, null: false
18
+ t.datetime :expires_at, null: false
19
+ t.datetime :used_at
20
+ t.string :created_by_ip
21
+ t.string :used_from_ip
22
+
23
+ t.timestamps
24
+ end
25
+
26
+ add_index :lean_cms_magic_links, :token, unique: true
27
+ add_index :lean_cms_magic_links, [:user_id, :purpose]
28
+ add_index :lean_cms_magic_links, :expires_at
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,16 @@
1
+ class CreatePaperTrailVersions < ActiveRecord::Migration[7.1]
2
+ # Idempotent on existing installs that already ran `paper_trail:install`.
3
+ def change
4
+ create_table :versions, if_not_exists: true do |t|
5
+ t.string :item_type, null: false
6
+ t.bigint :item_id, null: false
7
+ t.string :event, null: false
8
+ t.string :whodunnit
9
+ t.text :object
10
+ t.datetime :created_at
11
+ end
12
+
13
+ return if index_exists?(:versions, [:item_type, :item_id])
14
+ add_index :versions, [:item_type, :item_id]
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ class CreateActionTextTables < ActiveRecord::Migration[7.1]
2
+ # Mirrors `bin/rails action_text:install` output. Idempotent: hosts that
3
+ # already ran action_text:install separately are unaffected.
4
+ def change
5
+ create_table :action_text_rich_texts, if_not_exists: true do |t|
6
+ t.string :name, null: false
7
+ t.text :body
8
+ t.references :record, null: false, polymorphic: true, index: false
9
+ t.timestamps
10
+ end
11
+
12
+ return if index_exists?(:action_text_rich_texts, [:record_type, :record_id, :name], name: "index_action_text_rich_texts_uniqueness")
13
+ add_index :action_text_rich_texts,
14
+ [:record_type, :record_id, :name],
15
+ name: "index_action_text_rich_texts_uniqueness",
16
+ unique: true
17
+ end
18
+ end
@@ -0,0 +1,45 @@
1
+ class CreateActiveStorageTables < ActiveRecord::Migration[7.1]
2
+ # Mirrors `bin/rails active_storage:install` output. Idempotent.
3
+ def change
4
+ create_table :active_storage_blobs, if_not_exists: true do |t|
5
+ t.string :key, null: false
6
+ t.string :filename, null: false
7
+ t.string :content_type
8
+ t.text :metadata
9
+ t.string :service_name, null: false
10
+ t.bigint :byte_size, null: false
11
+ t.string :checksum
12
+ t.datetime :created_at, null: false
13
+ end
14
+ add_index :active_storage_blobs, :key, unique: true, if_not_exists: true
15
+
16
+ create_table :active_storage_attachments, if_not_exists: true do |t|
17
+ t.string :name, null: false
18
+ t.references :record, null: false, polymorphic: true, index: false
19
+ t.references :blob, null: false
20
+ t.datetime :created_at, null: false
21
+ end
22
+
23
+ unless index_exists?(:active_storage_attachments,
24
+ [:record_type, :record_id, :name, :blob_id],
25
+ name: "index_active_storage_attachments_uniqueness")
26
+ add_index :active_storage_attachments,
27
+ [:record_type, :record_id, :name, :blob_id],
28
+ name: "index_active_storage_attachments_uniqueness",
29
+ unique: true
30
+ end
31
+
32
+ create_table :active_storage_variant_records, if_not_exists: true do |t|
33
+ t.belongs_to :blob, null: false, index: false
34
+ t.string :variation_digest, null: false
35
+ end
36
+
37
+ return if index_exists?(:active_storage_variant_records,
38
+ [:blob_id, :variation_digest],
39
+ name: "index_active_storage_variant_records_uniqueness")
40
+ add_index :active_storage_variant_records,
41
+ [:blob_id, :variation_digest],
42
+ name: "index_active_storage_variant_records_uniqueness",
43
+ unique: true
44
+ end
45
+ end
@@ -0,0 +1,27 @@
1
+ class CreateNoticedTables < ActiveRecord::Migration[7.1]
2
+ # Mirrors `bin/rails noticed:install:migrations` output for noticed 3.x.
3
+ # Idempotent — hosts that ran `noticed:install:migrations` separately get
4
+ # no-ops on these tables.
5
+ def change
6
+ create_table :noticed_events, if_not_exists: true do |t|
7
+ t.string :type
8
+ t.belongs_to :record, polymorphic: true
9
+ if t.respond_to?(:jsonb)
10
+ t.jsonb :params
11
+ else
12
+ t.json :params
13
+ end
14
+ t.integer :notifications_count
15
+ t.timestamps
16
+ end
17
+
18
+ create_table :noticed_notifications, if_not_exists: true do |t|
19
+ t.string :type
20
+ t.belongs_to :event, null: false
21
+ t.belongs_to :recipient, polymorphic: true, null: false
22
+ t.datetime :read_at
23
+ t.datetime :seen_at
24
+ t.timestamps
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,54 @@
1
+ require "rails/generators"
2
+
3
+ module LeanCms
4
+ module Generators
5
+ class DemoGenerator < Rails::Generators::Base
6
+ source_root File.expand_path("templates", __dir__)
7
+
8
+ desc "Installs Lean CMS demo pages: Home, About, and Contact with all 9 content types"
9
+
10
+ def copy_pages_controller
11
+ template "pages_controller.rb", "app/controllers/pages_controller.rb"
12
+ end
13
+
14
+ def copy_views
15
+ directory "views/pages", "app/views/pages"
16
+ end
17
+
18
+ def copy_structure_yaml
19
+ template "lean_cms_structure.yml", "config/lean_cms_structure.yml",
20
+ force: options[:force]
21
+ end
22
+
23
+ def add_routes
24
+ route <<~RUBY
25
+ get 'about', to: 'pages#about', as: :about
26
+ get 'contact', to: 'pages#contact', as: :contact
27
+ post 'contact', to: 'pages#submit_contact'
28
+ root 'pages#home'
29
+ RUBY
30
+ end
31
+
32
+ def seed_content
33
+ say "Seeding demo content...", :yellow
34
+ rake "lean_cms:load_structure"
35
+ rescue StandardError => e
36
+ say " Could not auto-seed (run 'rails lean_cms:load_structure' manually): #{e.message}", :yellow
37
+ end
38
+
39
+ def print_instructions
40
+ say "\n#{"=" * 60}", :green
41
+ say "Lean CMS demo pages installed!", :green
42
+ say "=" * 60, :green
43
+ say ""
44
+ say "Demo pages:"
45
+ say " / -> Home (hero, features cards, intro text)"
46
+ say " /about -> About (rich text, image, bullets, boolean toggle)"
47
+ say " /contact -> Contact (URL, color, dropdown, form)"
48
+ say ""
49
+ say "Log in at /lean-cms to see in-context editing in action."
50
+ say ""
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,129 @@
1
+ pages:
2
+ home:
3
+ display_title: "Home"
4
+ page_order: 1
5
+ sections:
6
+ hero:
7
+ display_title: "Hero"
8
+ section_order: 1
9
+ fields:
10
+ heading:
11
+ label: "Headline"
12
+ type: text
13
+ default: "Build Faster, Ship Smarter"
14
+ subheading:
15
+ label: "Sub-headline"
16
+ type: text
17
+ default: "Lean CMS gives Rails developers in-context editing without the bloat."
18
+ body:
19
+ label: "Body Text"
20
+ type: rich_text
21
+ default: "<p>Everything your marketing site needs, nothing it doesn't.</p>"
22
+ cta_text:
23
+ label: "CTA Button Text"
24
+ type: text
25
+ default: "Get Started"
26
+ cta_url:
27
+ label: "CTA Button URL"
28
+ type: url
29
+ default: "/contact"
30
+ features:
31
+ display_title: "Features"
32
+ section_order: 2
33
+ fields:
34
+ heading:
35
+ label: "Section Heading"
36
+ type: text
37
+ default: "Why Developers Love Lean CMS"
38
+ cards:
39
+ max_cards: 6
40
+ type: feature
41
+ items:
42
+ - title: "In-Context Editing"
43
+ body: "Hover any section to reveal an edit button. Edit content without leaving the page."
44
+ icon: "checkmark-badge"
45
+ - title: "9 Content Types"
46
+ body: "Text, rich text, image, boolean, URL, color, dropdown, cards, and bullets."
47
+ icon: "sliders"
48
+ - title: "SQLite First"
49
+ body: "Optimized for SQLite in production. Simple deployments, no external database."
50
+ icon: "lightning-bolt"
51
+ - title: "Kamal Ready"
52
+ body: "Deploy with Kamal. Pre-built hooks keep content safe during deploys."
53
+ icon: "globe"
54
+
55
+ about:
56
+ display_title: "About"
57
+ page_order: 2
58
+ sections:
59
+ intro:
60
+ display_title: "Introduction"
61
+ section_order: 1
62
+ fields:
63
+ heading:
64
+ label: "Page Heading"
65
+ type: text
66
+ default: "About Lean CMS"
67
+ body:
68
+ label: "Body"
69
+ type: rich_text
70
+ default: "<p>Lean CMS is a Rails Engine built for developers who want a fast, maintainable CMS without an external SaaS dependency.</p>"
71
+ photo:
72
+ label: "Team Photo"
73
+ type: image
74
+ show_badge:
75
+ label: "Show Trust Badge"
76
+ type: boolean
77
+ default: true
78
+ badge_text:
79
+ label: "Badge Text"
80
+ type: text
81
+ default: "Open Source since 2025"
82
+ values:
83
+ display_title: "Our Values"
84
+ section_order: 2
85
+ fields:
86
+ heading:
87
+ label: "Section Heading"
88
+ type: text
89
+ default: "What We Stand For"
90
+ bullets:
91
+ max_items: 6
92
+ items:
93
+ - "Lean over bloated — add only what you need"
94
+ - "Developer experience first"
95
+ - "Content should be versioned and auditable"
96
+ - "Deployment should be boring"
97
+
98
+ contact:
99
+ display_title: "Contact"
100
+ page_order: 3
101
+ sections:
102
+ info:
103
+ display_title: "Contact Info"
104
+ section_order: 1
105
+ fields:
106
+ heading:
107
+ label: "Page Heading"
108
+ type: text
109
+ default: "Get In Touch"
110
+ body:
111
+ label: "Intro Text"
112
+ type: rich_text
113
+ default: "<p>Have a question or want to work together? Send us a message and we'll get back to you shortly.</p>"
114
+ map_url:
115
+ label: "Google Maps URL"
116
+ type: url
117
+ default: "https://maps.google.com"
118
+ accent_color:
119
+ label: "Accent Color"
120
+ type: color
121
+ default: "#2563eb"
122
+ preferred_contact:
123
+ label: "Preferred Contact Method"
124
+ type: dropdown
125
+ options:
126
+ - "Email"
127
+ - "Phone"
128
+ - "Live Chat"
129
+ default: "Email"
@@ -0,0 +1,30 @@
1
+ class PagesController < ApplicationController
2
+ # LeanCms::Authentication protects every controller by default. Opt the
3
+ # public-facing demo pages out so non-admins can actually see them.
4
+ allow_unauthenticated_access
5
+
6
+ def home
7
+ @page = LeanCms::Page.find_by(slug: 'home')
8
+ end
9
+
10
+ def about
11
+ @page = LeanCms::Page.find_by(slug: 'about')
12
+ end
13
+
14
+ def contact
15
+ @page = LeanCms::Page.find_by(slug: 'contact')
16
+ end
17
+
18
+ def submit_contact
19
+ LeanCms::FormSubmission.create!(
20
+ name: params[:name],
21
+ email: params[:email],
22
+ message: params[:message]
23
+ )
24
+ redirect_to contact_path, notice: "Message sent!"
25
+ rescue ActiveRecord::RecordInvalid => e
26
+ flash.now[:alert] = e.message
27
+ @page = LeanCms::Page.find_by(slug: 'contact')
28
+ render :contact, status: :unprocessable_entity
29
+ end
30
+ end
@@ -0,0 +1,40 @@
1
+ <%# Demo About page — showcases rich_text, image, boolean, and bullets content types %>
2
+
3
+ <%= cms_editable_section(page: 'about', section: 'intro', display_title: 'Introduction') do %>
4
+ <section class="bg-white py-20">
5
+ <div class="max-w-4xl mx-auto px-4 grid md:grid-cols-2 gap-12 items-center">
6
+ <div>
7
+ <h1 class="text-4xl font-bold text-gray-900 mb-6">
8
+ <%= page_content(@page, 'intro', 'heading', default: 'About Us') %>
9
+ </h1>
10
+ <div class="prose text-gray-700">
11
+ <%= page_content_html(@page, 'intro', 'body') %>
12
+ </div>
13
+
14
+ <%# boolean content type: show/hide a badge based on a toggle %>
15
+ <% if page_content?(@page, 'intro', 'show_badge') %>
16
+ <div class="mt-6 inline-flex items-center px-4 py-2 bg-blue-100 text-blue-800 rounded-full text-sm font-medium">
17
+ <%= page_content(@page, 'intro', 'badge_text', default: 'Trusted Since 2020') %>
18
+ </div>
19
+ <% end %>
20
+ </div>
21
+
22
+ <%# image content type %>
23
+ <% img = page_content_image_url(@page, 'intro', 'photo') %>
24
+ <% if img.present? %>
25
+ <%= image_tag img, alt: "About us", class: "rounded-xl shadow-lg w-full object-cover aspect-square" %>
26
+ <% end %>
27
+ </div>
28
+ </section>
29
+ <% end %>
30
+
31
+ <%= cms_editable_section(page: 'about', section: 'values', display_title: 'Our Values') do %>
32
+ <section class="bg-gray-50 py-16">
33
+ <div class="max-w-4xl mx-auto px-4">
34
+ <h2 class="text-3xl font-bold text-gray-900 mb-10">
35
+ <%= page_content(@page, 'values', 'heading', default: 'What We Stand For') %>
36
+ </h2>
37
+ <%= bullets_section('values') %>
38
+ </div>
39
+ </section>
40
+ <% end %>
@@ -0,0 +1,55 @@
1
+ <%# Demo Contact page — showcases url, color, dropdown, and form submission %>
2
+
3
+ <%= cms_editable_section(page: 'contact', section: 'info', display_title: 'Contact Info') do %>
4
+ <section class="bg-white py-20">
5
+ <div class="max-w-5xl mx-auto px-4 grid md:grid-cols-2 gap-16">
6
+ <!-- Contact details -->
7
+ <div>
8
+ <h1 class="text-4xl font-bold text-gray-900 mb-6">
9
+ <%= page_content(@page, 'info', 'heading', default: 'Get In Touch') %>
10
+ </h1>
11
+ <div class="prose text-gray-700 mb-8">
12
+ <%= page_content_html(@page, 'info', 'body') %>
13
+ </div>
14
+
15
+ <%# url content type %>
16
+ <% map_url = page_content(@page, 'info', 'map_url') %>
17
+ <% if map_url.present? %>
18
+ <%= link_to "View on Google Maps", map_url, target: "_blank", class: "text-blue-600 hover:underline" %>
19
+ <% end %>
20
+
21
+ <%# color content type — used as an accent for the contact card border %>
22
+ <% accent = page_content(@page, 'info', 'accent_color', default: '#2563eb') %>
23
+ <div class="mt-8 p-6 rounded-xl border-l-4" style="border-color: <%= accent %>;">
24
+ <%# dropdown content type — e.g. preferred contact method %>
25
+ <p class="text-sm text-gray-500 mb-1">Preferred contact method</p>
26
+ <p class="font-semibold text-gray-800">
27
+ <%= page_content(@page, 'info', 'preferred_contact', default: 'Email') %>
28
+ </p>
29
+ </div>
30
+ </div>
31
+
32
+ <!-- Contact form -->
33
+ <div>
34
+ <% if flash[:notice] %>
35
+ <p class="mb-4 text-green-700 bg-green-100 px-4 py-3 rounded-lg"><%= flash[:notice] %></p>
36
+ <% end %>
37
+ <%= form_with url: contact_path, method: :post, class: "space-y-4" do |f| %>
38
+ <div>
39
+ <%= f.label :name, "Your Name", class: "block text-sm font-medium text-gray-700 mb-1" %>
40
+ <%= f.text_field :name, required: true, class: "w-full border border-gray-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" %>
41
+ </div>
42
+ <div>
43
+ <%= f.label :email, "Email Address", class: "block text-sm font-medium text-gray-700 mb-1" %>
44
+ <%= f.email_field :email, required: true, class: "w-full border border-gray-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" %>
45
+ </div>
46
+ <div>
47
+ <%= f.label :message, "Message", class: "block text-sm font-medium text-gray-700 mb-1" %>
48
+ <%= f.text_area :message, required: true, rows: 5, class: "w-full border border-gray-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" %>
49
+ </div>
50
+ <%= f.submit "Send Message", class: "w-full px-6 py-3 bg-blue-600 text-white font-semibold rounded-lg hover:bg-blue-700 transition cursor-pointer" %>
51
+ <% end %>
52
+ </div>
53
+ </div>
54
+ </section>
55
+ <% end %>
@@ -0,0 +1,31 @@
1
+ <%# Demo Home page — showcases text, rich_text, url, and cards content types %>
2
+
3
+ <%= cms_editable_section(page: 'home', section: 'hero', display_title: 'Hero') do %>
4
+ <section class="bg-white py-20 text-center">
5
+ <div class="max-w-3xl mx-auto px-4">
6
+ <h1 class="text-4xl font-bold text-gray-900 mb-4">
7
+ <%= page_content(@page, 'hero', 'heading', default: 'Welcome') %>
8
+ </h1>
9
+ <p class="text-xl text-gray-600 mb-4">
10
+ <%= page_content(@page, 'hero', 'subheading') %>
11
+ </p>
12
+ <div class="prose mx-auto text-gray-700 mb-8">
13
+ <%= page_content_html(@page, 'hero', 'body') %>
14
+ </div>
15
+ <% cta_url = page_content(@page, 'hero', 'cta_url', default: '#') %>
16
+ <% cta_text = page_content(@page, 'hero', 'cta_text', default: 'Get Started') %>
17
+ <%= link_to cta_text, cta_url, class: "inline-block px-6 py-3 bg-blue-600 text-white font-semibold rounded-lg hover:bg-blue-700 transition" %>
18
+ </div>
19
+ </section>
20
+ <% end %>
21
+
22
+ <%= cms_editable_section(page: 'home', section: 'features', display_title: 'Features') do %>
23
+ <section class="bg-gray-50 py-16">
24
+ <div class="max-w-5xl mx-auto px-4">
25
+ <h2 class="text-3xl font-bold text-gray-900 text-center mb-12">
26
+ <%= page_content(@page, 'features', 'heading', default: 'Why Choose Us') %>
27
+ </h2>
28
+ <%= cards_section('features') %>
29
+ </div>
30
+ </section>
31
+ <% end %>