terrazzo 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 (161) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/Rakefile +11 -0
  4. data/app/controllers/terrazzo/application_controller.rb +208 -0
  5. data/app/views/terrazzo/application/edit.json.props +70 -0
  6. data/app/views/terrazzo/application/index.json.props +97 -0
  7. data/app/views/terrazzo/application/new.json.props +65 -0
  8. data/app/views/terrazzo/application/show.json.props +44 -0
  9. data/app/views/terrazzo/application/superglue.html.erb +5 -0
  10. data/config/locales/en.yml +28 -0
  11. data/lib/generators/terrazzo/dashboard/dashboard_generator.rb +118 -0
  12. data/lib/generators/terrazzo/dashboard/templates/controller.rb.erb +4 -0
  13. data/lib/generators/terrazzo/dashboard/templates/dashboard.rb.erb +36 -0
  14. data/lib/generators/terrazzo/field/field_generator.rb +42 -0
  15. data/lib/generators/terrazzo/field/templates/FormField.jsx.erb +19 -0
  16. data/lib/generators/terrazzo/field/templates/IndexField.jsx.erb +5 -0
  17. data/lib/generators/terrazzo/field/templates/ShowField.jsx.erb +5 -0
  18. data/lib/generators/terrazzo/field/templates/field.rb.erb +23 -0
  19. data/lib/generators/terrazzo/install/install_generator.rb +85 -0
  20. data/lib/generators/terrazzo/install/templates/application.js.erb +27 -0
  21. data/lib/generators/terrazzo/install/templates/application.json.props +27 -0
  22. data/lib/generators/terrazzo/install/templates/application.json.props.erb +17 -0
  23. data/lib/generators/terrazzo/install/templates/application_controller.rb.erb +24 -0
  24. data/lib/generators/terrazzo/install/templates/application_visit.js.erb +8 -0
  25. data/lib/generators/terrazzo/install/templates/flash_slice.js.erb +42 -0
  26. data/lib/generators/terrazzo/install/templates/page_to_page_mapping.js.erb +22 -0
  27. data/lib/generators/terrazzo/install/templates/store.js.erb +24 -0
  28. data/lib/generators/terrazzo/install/templates/superglue.html.erb.erb +20 -0
  29. data/lib/generators/terrazzo/routes/routes_generator.rb +38 -0
  30. data/lib/generators/terrazzo/views/edit_generator.rb +28 -0
  31. data/lib/generators/terrazzo/views/field_generator.rb +32 -0
  32. data/lib/generators/terrazzo/views/index_generator.rb +24 -0
  33. data/lib/generators/terrazzo/views/layout_generator.rb +26 -0
  34. data/lib/generators/terrazzo/views/navigation_generator.rb +24 -0
  35. data/lib/generators/terrazzo/views/new_generator.rb +28 -0
  36. data/lib/generators/terrazzo/views/show_generator.rb +24 -0
  37. data/lib/generators/terrazzo/views/templates/components/FlashMessages.jsx +26 -0
  38. data/lib/generators/terrazzo/views/templates/components/Layout.jsx +23 -0
  39. data/lib/generators/terrazzo/views/templates/components/Pagination.jsx +69 -0
  40. data/lib/generators/terrazzo/views/templates/components/SearchBar.jsx +35 -0
  41. data/lib/generators/terrazzo/views/templates/components/SortableHeader.jsx +29 -0
  42. data/lib/generators/terrazzo/views/templates/components/app-sidebar.jsx +62 -0
  43. data/lib/generators/terrazzo/views/templates/components/site-header.jsx +19 -0
  44. data/lib/generators/terrazzo/views/templates/components/ui/avatar.jsx +35 -0
  45. data/lib/generators/terrazzo/views/templates/components/ui/badge.jsx +34 -0
  46. data/lib/generators/terrazzo/views/templates/components/ui/button.jsx +47 -0
  47. data/lib/generators/terrazzo/views/templates/components/ui/card.jsx +50 -0
  48. data/lib/generators/terrazzo/views/templates/components/ui/dropdown-menu.jsx +155 -0
  49. data/lib/generators/terrazzo/views/templates/components/ui/field.jsx +28 -0
  50. data/lib/generators/terrazzo/views/templates/components/ui/index.js +106 -0
  51. data/lib/generators/terrazzo/views/templates/components/ui/input.jsx +19 -0
  52. data/lib/generators/terrazzo/views/templates/components/ui/label.jsx +16 -0
  53. data/lib/generators/terrazzo/views/templates/components/ui/pagination.jsx +85 -0
  54. data/lib/generators/terrazzo/views/templates/components/ui/popover.jsx +27 -0
  55. data/lib/generators/terrazzo/views/templates/components/ui/select.jsx +127 -0
  56. data/lib/generators/terrazzo/views/templates/components/ui/separator.jsx +23 -0
  57. data/lib/generators/terrazzo/views/templates/components/ui/sheet.jsx +109 -0
  58. data/lib/generators/terrazzo/views/templates/components/ui/sidebar.jsx +629 -0
  59. data/lib/generators/terrazzo/views/templates/components/ui/skeleton.jsx +10 -0
  60. data/lib/generators/terrazzo/views/templates/components/ui/table.jsx +83 -0
  61. data/lib/generators/terrazzo/views/templates/components/ui/textarea.jsx +18 -0
  62. data/lib/generators/terrazzo/views/templates/components/ui/tooltip.jsx +24 -0
  63. data/lib/generators/terrazzo/views/templates/fields/FieldRenderer.jsx +103 -0
  64. data/lib/generators/terrazzo/views/templates/fields/belongs_to/FormField.jsx +29 -0
  65. data/lib/generators/terrazzo/views/templates/fields/belongs_to/IndexField.jsx +7 -0
  66. data/lib/generators/terrazzo/views/templates/fields/belongs_to/ShowField.jsx +7 -0
  67. data/lib/generators/terrazzo/views/templates/fields/boolean/FormField.jsx +21 -0
  68. data/lib/generators/terrazzo/views/templates/fields/boolean/IndexField.jsx +12 -0
  69. data/lib/generators/terrazzo/views/templates/fields/boolean/ShowField.jsx +6 -0
  70. data/lib/generators/terrazzo/views/templates/fields/date/FormField.jsx +8 -0
  71. data/lib/generators/terrazzo/views/templates/fields/date/IndexField.jsx +7 -0
  72. data/lib/generators/terrazzo/views/templates/fields/date/ShowField.jsx +7 -0
  73. data/lib/generators/terrazzo/views/templates/fields/date_time/FormField.jsx +8 -0
  74. data/lib/generators/terrazzo/views/templates/fields/date_time/IndexField.jsx +7 -0
  75. data/lib/generators/terrazzo/views/templates/fields/date_time/ShowField.jsx +7 -0
  76. data/lib/generators/terrazzo/views/templates/fields/email/FormField.jsx +7 -0
  77. data/lib/generators/terrazzo/views/templates/fields/email/IndexField.jsx +10 -0
  78. data/lib/generators/terrazzo/views/templates/fields/email/ShowField.jsx +10 -0
  79. data/lib/generators/terrazzo/views/templates/fields/has_many/FormField.jsx +151 -0
  80. data/lib/generators/terrazzo/views/templates/fields/has_many/IndexField.jsx +8 -0
  81. data/lib/generators/terrazzo/views/templates/fields/has_many/ShowField.jsx +72 -0
  82. data/lib/generators/terrazzo/views/templates/fields/has_one/FormField.jsx +28 -0
  83. data/lib/generators/terrazzo/views/templates/fields/has_one/IndexField.jsx +7 -0
  84. data/lib/generators/terrazzo/views/templates/fields/has_one/ShowField.jsx +7 -0
  85. data/lib/generators/terrazzo/views/templates/fields/hstore/FormField.jsx +120 -0
  86. data/lib/generators/terrazzo/views/templates/fields/hstore/IndexField.jsx +15 -0
  87. data/lib/generators/terrazzo/views/templates/fields/hstore/ShowField.jsx +24 -0
  88. data/lib/generators/terrazzo/views/templates/fields/index.js +81 -0
  89. data/lib/generators/terrazzo/views/templates/fields/number/FormField.jsx +9 -0
  90. data/lib/generators/terrazzo/views/templates/fields/number/IndexField.jsx +9 -0
  91. data/lib/generators/terrazzo/views/templates/fields/number/ShowField.jsx +9 -0
  92. data/lib/generators/terrazzo/views/templates/fields/password/FormField.jsx +7 -0
  93. data/lib/generators/terrazzo/views/templates/fields/password/IndexField.jsx +6 -0
  94. data/lib/generators/terrazzo/views/templates/fields/password/ShowField.jsx +6 -0
  95. data/lib/generators/terrazzo/views/templates/fields/polymorphic/FormField.jsx +58 -0
  96. data/lib/generators/terrazzo/views/templates/fields/polymorphic/IndexField.jsx +7 -0
  97. data/lib/generators/terrazzo/views/templates/fields/polymorphic/ShowField.jsx +7 -0
  98. data/lib/generators/terrazzo/views/templates/fields/rich_text/FormField.jsx +21 -0
  99. data/lib/generators/terrazzo/views/templates/fields/rich_text/IndexField.jsx +8 -0
  100. data/lib/generators/terrazzo/views/templates/fields/rich_text/ShowField.jsx +6 -0
  101. data/lib/generators/terrazzo/views/templates/fields/select/FormField.jsx +29 -0
  102. data/lib/generators/terrazzo/views/templates/fields/select/IndexField.jsx +8 -0
  103. data/lib/generators/terrazzo/views/templates/fields/select/ShowField.jsx +5 -0
  104. data/lib/generators/terrazzo/views/templates/fields/shared/TextInputFormField.jsx +24 -0
  105. data/lib/generators/terrazzo/views/templates/fields/string/FormField.jsx +7 -0
  106. data/lib/generators/terrazzo/views/templates/fields/string/IndexField.jsx +5 -0
  107. data/lib/generators/terrazzo/views/templates/fields/string/ShowField.jsx +5 -0
  108. data/lib/generators/terrazzo/views/templates/fields/text/FormField.jsx +20 -0
  109. data/lib/generators/terrazzo/views/templates/fields/text/IndexField.jsx +5 -0
  110. data/lib/generators/terrazzo/views/templates/fields/text/ShowField.jsx +5 -0
  111. data/lib/generators/terrazzo/views/templates/fields/time/FormField.jsx +8 -0
  112. data/lib/generators/terrazzo/views/templates/fields/time/IndexField.jsx +7 -0
  113. data/lib/generators/terrazzo/views/templates/fields/time/ShowField.jsx +7 -0
  114. data/lib/generators/terrazzo/views/templates/fields/url/FormField.jsx +7 -0
  115. data/lib/generators/terrazzo/views/templates/fields/url/IndexField.jsx +10 -0
  116. data/lib/generators/terrazzo/views/templates/fields/url/ShowField.jsx +10 -0
  117. data/lib/generators/terrazzo/views/templates/pages/_form.jsx +76 -0
  118. data/lib/generators/terrazzo/views/templates/pages/edit.jsx +44 -0
  119. data/lib/generators/terrazzo/views/templates/pages/index.jsx +106 -0
  120. data/lib/generators/terrazzo/views/templates/pages/new.jsx +36 -0
  121. data/lib/generators/terrazzo/views/templates/pages/show.jsx +82 -0
  122. data/lib/generators/terrazzo/views/views_generator.rb +52 -0
  123. data/lib/terrazzo/base_dashboard.rb +88 -0
  124. data/lib/terrazzo/engine.rb +21 -0
  125. data/lib/terrazzo/field/associative.rb +56 -0
  126. data/lib/terrazzo/field/base.rb +114 -0
  127. data/lib/terrazzo/field/belongs_to.rb +53 -0
  128. data/lib/terrazzo/field/boolean.rb +9 -0
  129. data/lib/terrazzo/field/date.rb +10 -0
  130. data/lib/terrazzo/field/date_time.rb +10 -0
  131. data/lib/terrazzo/field/deferred.rb +50 -0
  132. data/lib/terrazzo/field/email.rb +15 -0
  133. data/lib/terrazzo/field/has_many.rb +98 -0
  134. data/lib/terrazzo/field/has_one.rb +33 -0
  135. data/lib/terrazzo/field/hstore.rb +37 -0
  136. data/lib/terrazzo/field/money.rb +33 -0
  137. data/lib/terrazzo/field/number.rb +17 -0
  138. data/lib/terrazzo/field/password.rb +16 -0
  139. data/lib/terrazzo/field/polymorphic.rb +36 -0
  140. data/lib/terrazzo/field/rich_text.rb +30 -0
  141. data/lib/terrazzo/field/select.rb +33 -0
  142. data/lib/terrazzo/field/string.rb +27 -0
  143. data/lib/terrazzo/field/text.rb +27 -0
  144. data/lib/terrazzo/field/time.rb +10 -0
  145. data/lib/terrazzo/field/url.rb +9 -0
  146. data/lib/terrazzo/filter.rb +26 -0
  147. data/lib/terrazzo/generator_helpers.rb +36 -0
  148. data/lib/terrazzo/namespace/resource.rb +39 -0
  149. data/lib/terrazzo/namespace.rb +34 -0
  150. data/lib/terrazzo/not_authorized_error.rb +4 -0
  151. data/lib/terrazzo/order.rb +71 -0
  152. data/lib/terrazzo/page/base.rb +12 -0
  153. data/lib/terrazzo/page/collection.rb +28 -0
  154. data/lib/terrazzo/page/form.rb +43 -0
  155. data/lib/terrazzo/page/show.rb +46 -0
  156. data/lib/terrazzo/resource_resolver.rb +40 -0
  157. data/lib/terrazzo/search.rb +56 -0
  158. data/lib/terrazzo/version.rb +3 -0
  159. data/lib/terrazzo.rb +47 -0
  160. data/terrazzo.gemspec +32 -0
  161. metadata +297 -0
@@ -0,0 +1,36 @@
1
+ require "terrazzo/base_dashboard"
2
+
3
+ class <%= class_name %>Dashboard < Terrazzo::BaseDashboard
4
+ ATTRIBUTE_TYPES = {
5
+ <% attribute_types.each do |attr, type| -%>
6
+ <% if has_enum?(attr.to_s) -%>
7
+ <%= attr %>: Terrazzo::<%= type %>.with_options(collection: <%= enum_collection(attr.to_s).inspect %>),
8
+ <% else -%>
9
+ <%= attr %>: Terrazzo::<%= type %>,
10
+ <% end -%>
11
+ <% end -%>
12
+ }.freeze
13
+
14
+ COLLECTION_ATTRIBUTES = %i[
15
+ <% collection_attributes.each do |attr| -%>
16
+ <%= attr %>
17
+ <% end -%>
18
+ ].freeze
19
+
20
+ SHOW_PAGE_ATTRIBUTES = %i[
21
+ <% show_page_attributes.each do |attr| -%>
22
+ <%= attr %>
23
+ <% end -%>
24
+ ].freeze
25
+
26
+ FORM_ATTRIBUTES = %i[
27
+ <% form_attributes.each do |attr| -%>
28
+ <%= attr %>
29
+ <% end -%>
30
+ ].freeze
31
+
32
+ # COLLECTION_FILTERS = {
33
+ # active: ->(resources) { resources.where(active: true) },
34
+ # recent: ->(resources) { resources.where("created_at > ?", 30.days.ago) },
35
+ # }.freeze
36
+ end
@@ -0,0 +1,42 @@
1
+ require "rails/generators"
2
+
3
+ module Terrazzo
4
+ module Generators
5
+ class FieldGenerator < Rails::Generators::NamedBase
6
+ source_root File.expand_path("templates", __dir__)
7
+
8
+ class_option :namespace, type: :string, default: "admin",
9
+ desc: "Admin namespace"
10
+
11
+ def create_field_class
12
+ template "field.rb.erb",
13
+ "app/fields/#{file_name}_field.rb"
14
+ end
15
+
16
+ def create_jsx_components
17
+ %w[IndexField ShowField FormField].each do |component|
18
+ template "#{component}.jsx.erb",
19
+ "app/views/#{namespace_name}/fields/#{file_name}/#{component}.jsx"
20
+ end
21
+ end
22
+
23
+ def show_registration_instructions
24
+ say "\nTo use your custom field, register it in your FieldRenderer.jsx:", :green
25
+ say " import { IndexField as #{class_name}Index } from \"./#{file_name}/IndexField\";"
26
+ say " import { ShowField as #{class_name}Show } from \"./#{file_name}/ShowField\";"
27
+ say " import { FormField as #{class_name}Form } from \"./#{file_name}/FormField\";"
28
+ say ""
29
+ say " registerFieldType(\"#{file_name}\", { index: #{class_name}Index, show: #{class_name}Show, form: #{class_name}Form });"
30
+ say ""
31
+ say "Then use it in your dashboard:"
32
+ say " #{file_name}: Terrazzo::Field::#{class_name},"
33
+ end
34
+
35
+ private
36
+
37
+ def namespace_name
38
+ options[:namespace]
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,19 @@
1
+ import React from "react";
2
+ import { Input } from "../../components/ui/input";
3
+ import { Label } from "../../components/ui/label";
4
+
5
+ export function FormField({ attribute, label, value, input, required }) {
6
+ return (
7
+ <div className="space-y-2">
8
+ <Label htmlFor={input.id}>
9
+ {label}{required && <span className="text-red-500 ml-1">*</span>}
10
+ </Label>
11
+ <Input
12
+ type="text"
13
+ id={input.id}
14
+ name={input.name}
15
+ defaultValue={value ?? ""}
16
+ />
17
+ </div>
18
+ );
19
+ }
@@ -0,0 +1,5 @@
1
+ import React from "react";
2
+
3
+ export function IndexField({ value }) {
4
+ return <span>{String(value ?? "")}</span>;
5
+ }
@@ -0,0 +1,5 @@
1
+ import React from "react";
2
+
3
+ export function ShowField({ value }) {
4
+ return <span>{String(value ?? "")}</span>;
5
+ }
@@ -0,0 +1,23 @@
1
+ module Terrazzo
2
+ module Field
3
+ class <%= class_name %> < Base
4
+ def serialize_value(mode)
5
+ data
6
+ end
7
+
8
+ def serializable_options
9
+ {}
10
+ end
11
+
12
+ class << self
13
+ def searchable?
14
+ false
15
+ end
16
+
17
+ def sortable?
18
+ true
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,85 @@
1
+ require "rails/generators"
2
+
3
+ module Terrazzo
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root File.expand_path("templates", __dir__)
7
+
8
+ class_option :namespace, type: :string, default: "admin",
9
+ desc: "Admin namespace"
10
+ class_option :bundler, type: :string, default: "vite",
11
+ desc: "JavaScript bundler (vite or sprockets)"
12
+
13
+ def create_application_controller
14
+ template "application_controller.rb.erb",
15
+ "app/controllers/#{namespace_name}/application_controller.rb"
16
+ end
17
+
18
+ def create_layout
19
+ template "superglue.html.erb.erb",
20
+ "app/views/layouts/#{namespace_name}/application.html.erb"
21
+ end
22
+
23
+ def create_json_props_layout
24
+ copy_file "application.json.props",
25
+ "app/views/layouts/#{namespace_name}/application.json.props"
26
+ end
27
+
28
+ def create_js_entry_point
29
+ template "application.js.erb",
30
+ "app/javascript/#{namespace_name}/application.jsx"
31
+ end
32
+
33
+ def create_store
34
+ template "store.js.erb",
35
+ "app/javascript/#{namespace_name}/store.js"
36
+ end
37
+
38
+ def create_page_to_page_mapping
39
+ template "page_to_page_mapping.js.erb",
40
+ "app/javascript/#{namespace_name}/page_to_page_mapping.js"
41
+ end
42
+
43
+ def create_application_visit
44
+ template "application_visit.js.erb",
45
+ "app/javascript/#{namespace_name}/application_visit.js"
46
+ end
47
+
48
+ def create_flash_slice
49
+ template "flash_slice.js.erb",
50
+ "app/javascript/#{namespace_name}/slices/flash.js"
51
+ end
52
+
53
+ def run_views_generator
54
+ generate "terrazzo:views", "--namespace=#{namespace_name}"
55
+ end
56
+
57
+ def run_routes_generator
58
+ generate "terrazzo:routes", "--namespace=#{namespace_name}"
59
+ end
60
+
61
+ def run_dashboard_generators
62
+ application_models.each do |model|
63
+ generate "terrazzo:dashboard", model.name, "--namespace=#{namespace_name}", "--bundler=#{options[:bundler]}"
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def namespace_name
70
+ options[:namespace]
71
+ end
72
+
73
+ def vite?
74
+ options[:bundler] == "vite"
75
+ end
76
+
77
+ def application_models
78
+ Rails.application.eager_load! if defined?(Rails)
79
+ ApplicationRecord.descendants.reject(&:abstract_class?)
80
+ rescue
81
+ []
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,27 @@
1
+ import React from "react"
2
+ import { createRoot } from "react-dom/client"
3
+ import { Application } from "@thoughtbot/superglue"
4
+ import { buildVisitAndRemote } from "./application_visit"
5
+ import { pageToPageMapping } from "./page_to_page_mapping"
6
+ import { store } from "./store"
7
+
8
+ if (typeof window !== "undefined") {
9
+ document.addEventListener("DOMContentLoaded", function () {
10
+ const appEl = document.getElementById("superglue-app")
11
+ const location = window.location
12
+
13
+ if (appEl) {
14
+ const root = createRoot(appEl)
15
+ root.render(
16
+ <Application
17
+ initialPage={window.SUPERGLUE_INITIAL_PAGE_STATE}
18
+ baseUrl={location.origin}
19
+ path={location.pathname + location.search + location.hash}
20
+ store={store}
21
+ mapping={pageToPageMapping}
22
+ buildVisitAndRemote={buildVisitAndRemote}
23
+ />
24
+ )
25
+ }
26
+ })
27
+ }
@@ -0,0 +1,27 @@
1
+ path = request.format.json? ? param_to_dig_path(params[:props_at]) : nil
2
+
3
+ json.data(dig: path) do
4
+ yield
5
+ end
6
+
7
+ json.componentIdentifier terrazzo_page_identifier
8
+ json.defers json.deferred!
9
+ json.fragments json.fragments!
10
+ json.assets []
11
+
12
+ if protect_against_forgery?
13
+ json.csrfToken form_authenticity_token
14
+ end
15
+
16
+ if path
17
+ json.action "graft"
18
+ json.path params[:props_at]
19
+ end
20
+
21
+ json.restoreStrategy "fromCacheAndRevisitInBackground"
22
+
23
+ json.renderedAt Time.now.to_i
24
+
25
+ json.slices do
26
+ json.flash flash.to_h
27
+ end
@@ -0,0 +1,17 @@
1
+ json.data(search: request.path) do
2
+ yield
3
+ end
4
+
5
+ json.componentIdentifier local_assigns[:virtual_path_of_template]
6
+ json.assets do
7
+ json.array! []
8
+ end
9
+
10
+ json.csrfToken form_authenticity_token
11
+ json.restoreStrategy "fromCacheAndRevisitInBackground"
12
+
13
+ json.flash do
14
+ flash.each do |type, message|
15
+ json.set!(type, message)
16
+ end
17
+ end
@@ -0,0 +1,24 @@
1
+ module <%= namespace_name.camelize %>
2
+ class ApplicationController < Terrazzo::ApplicationController
3
+ layout "<%= namespace_name %>/application"
4
+ superglue_template "terrazzo/application/superglue"
5
+
6
+ # before_action :authenticate_admin
7
+
8
+ # Override to limit which records are visible
9
+ # def scoped_resource
10
+ # resource_class.all
11
+ # end
12
+
13
+ # Override to control access to actions
14
+ # def authorized_action?(resource, action)
15
+ # true
16
+ # end
17
+
18
+ private
19
+
20
+ # def authenticate_admin
21
+ # redirect_to root_path unless current_user&.admin?
22
+ # end
23
+ end
24
+ end
@@ -0,0 +1,8 @@
1
+ import { visit, remote } from "@thoughtbot/superglue/action_creators"
2
+
3
+ export function buildVisitAndRemote(ref, store, pageToPageMapping) {
4
+ const appVisit = visit(ref, store, pageToPageMapping)
5
+ const appRemote = remote(ref, store, pageToPageMapping)
6
+
7
+ return { visit: appVisit, remote: appRemote }
8
+ }
@@ -0,0 +1,42 @@
1
+ import { createSlice } from "@reduxjs/toolkit"
2
+ import { saveResponse, beforeVisit } from "@thoughtbot/superglue"
3
+
4
+ export const flashSlice = createSlice({
5
+ name: "flash",
6
+ initialState: {},
7
+ reducers: {
8
+ clearFlash(state, { payload }) {
9
+ const key = payload
10
+ if (!key) {
11
+ return {}
12
+ }
13
+
14
+ delete state[key]
15
+
16
+ return {
17
+ ...state
18
+ }
19
+ },
20
+ flash(state, { payload }) {
21
+ return {
22
+ ...state,
23
+ ...payload
24
+ }
25
+ }
26
+ },
27
+ extraReducers: builder => {
28
+ builder.addCase(beforeVisit, (_state, _action) => {
29
+ return {}
30
+ })
31
+ builder.addCase(saveResponse, (state, action) => {
32
+ const { page } = action.payload
33
+
34
+ return {
35
+ ...state,
36
+ ...page.slices.flash
37
+ }
38
+ })
39
+ }
40
+ })
41
+
42
+ export const { clearFlash, flash } = flashSlice.actions
@@ -0,0 +1,22 @@
1
+ <% if vite? -%>
2
+ const pages = import.meta.glob("../../views/<%= namespace_name %>/**/*.jsx", { eager: true })
3
+
4
+ export const pageToPageMapping = {}
5
+
6
+ for (const key in pages) {
7
+ const identifier = key.replace("../../views/", "").replace(/\.jsx$/, "")
8
+ pageToPageMapping[identifier] = pages[key].default
9
+ }
10
+ <% else -%>
11
+ import AdminIndex from "../../views/<%= namespace_name %>/application/index"
12
+ import AdminShow from "../../views/<%= namespace_name %>/application/show"
13
+ import AdminNew from "../../views/<%= namespace_name %>/application/new"
14
+ import AdminEdit from "../../views/<%= namespace_name %>/application/edit"
15
+
16
+ export const pageToPageMapping = {
17
+ '<%= namespace_name %>/application/index': AdminIndex,
18
+ '<%= namespace_name %>/application/show': AdminShow,
19
+ '<%= namespace_name %>/application/new': AdminNew,
20
+ '<%= namespace_name %>/application/edit': AdminEdit,
21
+ }
22
+ <% end -%>
@@ -0,0 +1,24 @@
1
+ import { configureStore } from "@reduxjs/toolkit"
2
+ import { flashSlice } from "./slices/flash"
3
+ import {
4
+ beforeVisit,
5
+ beforeFetch,
6
+ beforeRemote,
7
+ rootReducer,
8
+ } from "@thoughtbot/superglue"
9
+
10
+ const { pages, superglue } = rootReducer
11
+
12
+ export const store = configureStore({
13
+ middleware: (getDefaultMiddleware) =>
14
+ getDefaultMiddleware({
15
+ serializableCheck: {
16
+ ignoredActions: [beforeFetch.type, beforeVisit.type, beforeRemote.type],
17
+ },
18
+ }),
19
+ reducer: {
20
+ superglue,
21
+ pages,
22
+ flash: flashSlice.reducer,
23
+ },
24
+ })
@@ -0,0 +1,20 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <%%= csrf_meta_tags %>
7
+ <%%= csp_meta_tag %>
8
+ <%%= yield :head %>
9
+ <% if vite? -%>
10
+ <%%= vite_react_refresh_tag %>
11
+ <%%= vite_javascript_tag "<%= namespace_name %>/application" %>
12
+ <% else -%>
13
+ <%%= javascript_include_tag "<%= namespace_name %>", type: "module" %>
14
+ <% end -%>
15
+ <%%= stylesheet_link_tag "<%= namespace_name %>", "data-turbo-track": "reload" %>
16
+ </head>
17
+ <body>
18
+ <%%= yield %>
19
+ </body>
20
+ </html>
@@ -0,0 +1,38 @@
1
+ require "rails/generators"
2
+
3
+ module Terrazzo
4
+ module Generators
5
+ class RoutesGenerator < Rails::Generators::Base
6
+ source_root File.expand_path("templates", __dir__)
7
+
8
+ class_option :namespace, type: :string, default: "admin",
9
+ desc: "Admin namespace"
10
+
11
+ def insert_routes
12
+ namespace_name = options[:namespace]
13
+ route_content = <<~RUBY.indent(2)
14
+ namespace :#{namespace_name} do
15
+ root to: "#{first_resource}#index"
16
+ end
17
+ RUBY
18
+
19
+ route route_content.strip
20
+ end
21
+
22
+ private
23
+
24
+ def first_resource
25
+ # Default to "customers" if no models found
26
+ models = application_models
27
+ models.any? ? models.first.model_name.plural : "dashboard"
28
+ end
29
+
30
+ def application_models
31
+ Rails.application.eager_load! if defined?(Rails)
32
+ ApplicationRecord.descendants.reject(&:abstract_class?)
33
+ rescue
34
+ []
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,28 @@
1
+ require "rails/generators"
2
+
3
+ module Terrazzo
4
+ module Generators
5
+ module Views
6
+ class EditGenerator < Rails::Generators::Base
7
+ source_root File.expand_path("templates", __dir__)
8
+
9
+ class_option :namespace, type: :string, default: "admin",
10
+ desc: "Admin namespace"
11
+
12
+ def copy_edit_template
13
+ copy_file "pages/edit.jsx", "app/views/#{namespace_name}/application/edit.jsx"
14
+ end
15
+
16
+ def copy_form_partial
17
+ copy_file "pages/_form.jsx", "app/views/#{namespace_name}/application/_form.jsx"
18
+ end
19
+
20
+ private
21
+
22
+ def namespace_name
23
+ options[:namespace]
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,32 @@
1
+ require "rails/generators"
2
+
3
+ module Terrazzo
4
+ module Generators
5
+ module Views
6
+ class FieldGenerator < Rails::Generators::Base
7
+ source_root File.expand_path("templates", __dir__)
8
+
9
+ class_option :namespace, type: :string, default: "admin",
10
+ desc: "Admin namespace"
11
+
12
+ argument :field_type, type: :string, required: false, default: "all",
13
+ desc: "Specific field type to copy (e.g., string, number) or 'all'"
14
+
15
+ def copy_field_templates
16
+ if field_type == "all"
17
+ directory "fields", "app/views/#{namespace_name}/fields"
18
+ else
19
+ directory "fields/shared", "app/views/#{namespace_name}/fields/shared"
20
+ directory "fields/#{field_type}", "app/views/#{namespace_name}/fields/#{field_type}"
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def namespace_name
27
+ options[:namespace]
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,24 @@
1
+ require "rails/generators"
2
+
3
+ module Terrazzo
4
+ module Generators
5
+ module Views
6
+ class IndexGenerator < Rails::Generators::Base
7
+ source_root File.expand_path("templates", __dir__)
8
+
9
+ class_option :namespace, type: :string, default: "admin",
10
+ desc: "Admin namespace"
11
+
12
+ def copy_index_template
13
+ copy_file "pages/index.jsx", "app/views/#{namespace_name}/application/index.jsx"
14
+ end
15
+
16
+ private
17
+
18
+ def namespace_name
19
+ options[:namespace]
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,26 @@
1
+ require "rails/generators"
2
+
3
+ module Terrazzo
4
+ module Generators
5
+ module Views
6
+ class LayoutGenerator < Rails::Generators::Base
7
+ source_root File.expand_path("templates", __dir__)
8
+
9
+ class_option :namespace, type: :string, default: "admin",
10
+ desc: "Admin namespace"
11
+
12
+ def copy_layout_components
13
+ %w[Layout.jsx app-sidebar.jsx site-header.jsx FlashMessages.jsx].each do |file|
14
+ copy_file "components/#{file}", "app/views/#{namespace_name}/components/#{file}"
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def namespace_name
21
+ options[:namespace]
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,24 @@
1
+ require "rails/generators"
2
+
3
+ module Terrazzo
4
+ module Generators
5
+ module Views
6
+ class NavigationGenerator < Rails::Generators::Base
7
+ source_root File.expand_path("templates", __dir__)
8
+
9
+ class_option :namespace, type: :string, default: "admin",
10
+ desc: "Admin namespace"
11
+
12
+ def copy_navigation
13
+ copy_file "components/app-sidebar.jsx", "app/views/#{namespace_name}/components/app-sidebar.jsx"
14
+ end
15
+
16
+ private
17
+
18
+ def namespace_name
19
+ options[:namespace]
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,28 @@
1
+ require "rails/generators"
2
+
3
+ module Terrazzo
4
+ module Generators
5
+ module Views
6
+ class NewGenerator < Rails::Generators::Base
7
+ source_root File.expand_path("templates", __dir__)
8
+
9
+ class_option :namespace, type: :string, default: "admin",
10
+ desc: "Admin namespace"
11
+
12
+ def copy_new_template
13
+ copy_file "pages/new.jsx", "app/views/#{namespace_name}/application/new.jsx"
14
+ end
15
+
16
+ def copy_form_partial
17
+ copy_file "pages/_form.jsx", "app/views/#{namespace_name}/application/_form.jsx"
18
+ end
19
+
20
+ private
21
+
22
+ def namespace_name
23
+ options[:namespace]
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,24 @@
1
+ require "rails/generators"
2
+
3
+ module Terrazzo
4
+ module Generators
5
+ module Views
6
+ class ShowGenerator < Rails::Generators::Base
7
+ source_root File.expand_path("templates", __dir__)
8
+
9
+ class_option :namespace, type: :string, default: "admin",
10
+ desc: "Admin namespace"
11
+
12
+ def copy_show_template
13
+ copy_file "pages/show.jsx", "app/views/#{namespace_name}/application/show.jsx"
14
+ end
15
+
16
+ private
17
+
18
+ def namespace_name
19
+ options[:namespace]
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end