maquina 0.1.0 → 0.2.1
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/.overmind.env +1 -0
- data/Gemfile +20 -0
- data/Gemfile.lock +346 -0
- data/Procfile.dev +3 -0
- data/app/assets/images/maquina/.keep +0 -0
- data/app/assets/javascripts/maquina/controllers/file_controller.js +1 -1
- data/app/assets/javascripts/maquina/controllers/modal_controller.js +1 -1
- data/app/assets/javascripts/maquina/controllers/modal_open_controller.js +3 -8
- data/app/assets/stylesheets/maquina/application.tailwind.css +96 -97
- data/app/controllers/concerns/.keep +0 -0
- data/app/controllers/concerns/maquina/authenticate.rb +8 -1
- data/app/controllers/concerns/maquina/resourceful.rb +9 -9
- data/app/models/concerns/.keep +0 -0
- data/app/models/concerns/maquina/blockeable.rb +13 -3
- data/app/models/concerns/maquina/searchable.rb +2 -2
- data/app/models/maquina/application_record.rb +3 -3
- data/app/models/maquina/current.rb +2 -1
- data/app/models/maquina/membership.rb +10 -0
- data/app/models/maquina/organization.rb +7 -0
- data/app/models/maquina/plan.rb +1 -3
- data/app/models/maquina/user.rb +8 -3
- data/app/policies/maquina/application_policy.rb +3 -6
- data/app/policies/maquina/invitation_policy.rb +2 -2
- data/app/policies/maquina/navigation_policy.rb +2 -2
- data/app/policies/maquina/user_policy.rb +7 -3
- data/app/views/layouts/maquina/application.html.erb +1 -2
- data/app/views/layouts/maquina/sessions.html.erb +1 -2
- data/app/views/maquina/application/_navbar.html.erb +1 -1
- data/app/views/maquina/application/components/file_component.rb +38 -5
- data/app/views/maquina/application/form.rb +3 -2
- data/app/views/maquina/application/index_header.rb +1 -1
- data/app/views/maquina/application/index_modal.rb +1 -3
- data/config/importmap.rb +12 -0
- data/db/migrate/20230829183530_create_maquina_organizations.rb +11 -0
- data/db/migrate/20230829192656_create_maquina_memberships.rb +15 -0
- data/lib/generators/maquina/install_generator.rb +0 -4
- data/lib/generators/maquina/tailwind_config/USAGE +14 -0
- data/lib/generators/maquina/tailwind_config/tailwind_config_generator.rb +17 -3
- data/lib/generators/maquina/tailwind_config/templates/app/assets/stylesheets/maquina.css +96 -0
- data/lib/generators/maquina/tailwind_config/templates/lib/generators/tailwind_config/tailwind_config_generator.rb.tt +11 -0
- data/lib/generators/maquina/tailwind_config/templates/{app/assets/config/maquina → lib/generators/tailwind_config/templates/config}/tailwind.config.js.tt +4 -0
- data/lib/generators/maquina/tailwind_config/templates/lib/tasks/tailwind.rake.tt +11 -0
- data/lib/generators/maquina/templates/config/initializers/maquina.rb.tt +6 -0
- data/lib/maquina/engine.rb +0 -4
- data/lib/maquina/version.rb +1 -1
- data/lib/maquina.rb +11 -4
- metadata +23 -10
- data/config/definitions.rb +0 -1
- data/config/initializers/importmap.rb +0 -17
- data/lib/generators/maquina/templates/config/initializers/maquina.rb +0 -3
- data/lib/tasks/tailwind.rake +0 -25
@@ -16,6 +16,7 @@ module Maquina
|
|
16
16
|
|
17
17
|
included do
|
18
18
|
class_attribute :resource_class, instance_writer: false
|
19
|
+
class_attribute :namespace, instance_writer: false
|
19
20
|
class_attribute :find_by_param, instance_writer: false
|
20
21
|
class_attribute :list_attributes, instance_writer: false
|
21
22
|
class_attribute :form_attributes, instance_writer: false
|
@@ -28,18 +29,16 @@ module Maquina
|
|
28
29
|
|
29
30
|
# Route proxies
|
30
31
|
|
32
|
+
def namespace_path
|
33
|
+
@namespace_path ||= namespace.present? ? "#{namespace&.to_s&.underscore}_" : ""
|
34
|
+
end
|
35
|
+
|
31
36
|
def base_singular_path_name
|
32
|
-
@base_path_name ||=
|
33
|
-
namespace_path = ""
|
34
|
-
"#{namespace_path}#{resource_class.model_name.singular_route_key}_path"
|
35
|
-
end
|
37
|
+
@base_path_name ||= "#{namespace_path}#{resource_class.model_name.singular_route_key}_path"
|
36
38
|
end
|
37
39
|
|
38
40
|
def base_plural_path_name
|
39
|
-
@base_plural_path_name ||=
|
40
|
-
namespace_path = ""
|
41
|
-
"#{namespace_path}#{resource_class.model_name.route_key}_path"
|
42
|
-
end
|
41
|
+
@base_plural_path_name ||= "#{namespace_path}#{resource_class.model_name.route_key}_path"
|
43
42
|
end
|
44
43
|
|
45
44
|
def new_resource_path(options = {})
|
@@ -142,13 +141,14 @@ module Maquina
|
|
142
141
|
end
|
143
142
|
|
144
143
|
class_methods do
|
145
|
-
def resourceful(resource_class: nil, find_by_param: :id, only: [], except: [], list_attributes: [], form_attributes: [], show_attributes: [], policy_class: nil)
|
144
|
+
def resourceful(resource_class: nil, namespace: nil, find_by_param: :id, only: [], except: [], list_attributes: [], form_attributes: [], show_attributes: [], policy_class: nil)
|
146
145
|
self.resource_class = resource_class || controller_path.classify.safe_constantize
|
147
146
|
self.find_by_param = find_by_param || :id
|
148
147
|
self.list_attributes = Array(list_attributes).compact
|
149
148
|
self.form_attributes = Array(form_attributes).compact
|
150
149
|
self.show_attributes = Array(show_attributes).compact
|
151
150
|
self.policy_class = policy_class
|
151
|
+
self.namespace = namespace
|
152
152
|
|
153
153
|
valid_rest_actions = {
|
154
154
|
index: Maquina::Index,
|
File without changes
|
@@ -4,7 +4,16 @@ module Maquina
|
|
4
4
|
module Blockeable
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
|
-
included do
|
7
|
+
included do |base|
|
8
|
+
if base.eql?(Maquina::User)
|
9
|
+
define_method(:memberships_blocked?) do
|
10
|
+
return false if management?
|
11
|
+
|
12
|
+
return @memberships_blocked if !@memberships_blocked.nil?
|
13
|
+
@memberships_blocked = !memberships.includes(:organization).where(blocked_at: nil).where(maquina_organizations: {active: true}).exists?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
8
17
|
if has_attribute?(:blocked_at) && has_attribute?(:temporary_blocked_at)
|
9
18
|
define_method(:blocked?) do
|
10
19
|
temporary_blocked_until = nil
|
@@ -12,13 +21,14 @@ module Maquina
|
|
12
21
|
temporary_blocked_until = temporary_blocked_at.since(Maquina.configuration.temporary_block)
|
13
22
|
end
|
14
23
|
|
15
|
-
blocked_at.present? || (temporary_blocked_until.present? && temporary_blocked_until > Time.zone.now)
|
24
|
+
blocked_at.present? || (temporary_blocked_until.present? && temporary_blocked_until > Time.zone.now) ||
|
25
|
+
(respond_to?(:memberships_blocked?) && memberships_blocked?)
|
16
26
|
end
|
17
27
|
|
18
28
|
scope :unblocked, -> { where(blocked_at: nil).where("(coalesce(temporary_blocked_at + interval '? minutes', now())) <= now()", Maquina.configuration.temporary_block&.in_minutes&.to_i || 0) }
|
19
29
|
elsif has_attribute(:blocked_at)
|
20
30
|
define_method(:blocked?) do
|
21
|
-
blocked_at.present?
|
31
|
+
blocked_at.present? || (respond_to?(:memberships_blocked?) && memberships_blocked?)
|
22
32
|
end
|
23
33
|
|
24
34
|
scope :unblocked, -> { where(blocked_at: nil) }
|
@@ -11,13 +11,13 @@ module Maquina
|
|
11
11
|
end
|
12
12
|
|
13
13
|
class_methods do
|
14
|
-
def search_scope(fields: [], options: {})
|
14
|
+
def search_scope(fields: [], options: {}, associated_against: {})
|
15
15
|
return if fields.empty?
|
16
16
|
|
17
17
|
default_options = {tsearch: {prefix: true, any_word: true}}
|
18
18
|
search_options = options.deep_merge(default_options)
|
19
19
|
|
20
|
-
pg_search_scope :search_full, against: fields, using: search_options # , ignoring: :accents
|
20
|
+
pg_search_scope :search_full, against: fields, using: search_options, associated_against: associated_against # , ignoring: :accents
|
21
21
|
end
|
22
22
|
end
|
23
23
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Maquina
|
4
4
|
class Current < ActiveSupport::CurrentAttributes
|
5
|
-
attribute :active_session, :user
|
5
|
+
attribute :active_session, :user, :membership
|
6
6
|
|
7
7
|
def signed_in?
|
8
8
|
return false if active_session.blank?
|
@@ -12,6 +12,7 @@ module Maquina
|
|
12
12
|
def active_session=(value)
|
13
13
|
super
|
14
14
|
self.user = value&.user
|
15
|
+
self.membership = user.default_membership
|
15
16
|
end
|
16
17
|
|
17
18
|
def management?
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Maquina
|
4
|
+
class Membership < ApplicationRecord
|
5
|
+
belongs_to :organization, class_name: "Maquina::Organization", foreign_key: :maquina_organization_id
|
6
|
+
belongs_to :user, class_name: "Maquina::User", foreign_key: :maquina_user_id
|
7
|
+
|
8
|
+
enum role: Maquina.configuration.membership_roles
|
9
|
+
end
|
10
|
+
end
|
data/app/models/maquina/plan.rb
CHANGED
@@ -25,14 +25,12 @@ module Maquina
|
|
25
25
|
# attributes and use the provided methods and scopes for querying and manipulating plan data.
|
26
26
|
|
27
27
|
class Plan < ApplicationRecord
|
28
|
-
|
28
|
+
has_many :organizations, class_name: "Maquina::Organization", foreign_key: :maquina_plan_id, dependent: :nullify
|
29
29
|
|
30
30
|
monetize :price_cents
|
31
31
|
|
32
32
|
validates :price, numericality: {greater_than_or_equal_to: 0}, if: ->(plan) { plan.free? }
|
33
33
|
validates :price, numericality: {greater_than: 0}, if: ->(plan) { !plan.free? }
|
34
34
|
validates :name, presence: true, uniqueness: true
|
35
|
-
|
36
|
-
search_scope(fields: [:name])
|
37
35
|
end
|
38
36
|
end
|
data/app/models/maquina/user.rb
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
module Maquina
|
4
4
|
class User < ApplicationRecord
|
5
|
-
include Maquina::Searchable
|
6
5
|
include Maquina::RetainPasswords
|
7
6
|
include Maquina::AuthenticateBy
|
8
7
|
include Maquina::Blockeable
|
@@ -11,19 +10,25 @@ module Maquina
|
|
11
10
|
PASSWORD_COMPLEXITY_REGEX = /\A(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&#-=+])[A-Za-z\d@$!%*?&#-=+]{8,}\z/
|
12
11
|
has_secure_password
|
13
12
|
|
13
|
+
has_many :memberships, class_name: "Maquina::Membership", foreign_key: :maquina_user_id, inverse_of: :user
|
14
|
+
|
14
15
|
validates :email, presence: true, uniqueness: true, format: {with: URI::MailTo::EMAIL_REGEXP}
|
15
16
|
validates :password, format: {with: PASSWORD_COMPLEXITY_REGEX}, unless: ->(user) { user.password.blank? }
|
16
17
|
|
17
18
|
before_save :downcase_email
|
18
19
|
|
19
|
-
search_scope(fields: [:email])
|
20
|
-
|
21
20
|
def expired_password?
|
22
21
|
return false if password_expires_at.blank?
|
23
22
|
|
24
23
|
password_expires_at < Time.zone.now
|
25
24
|
end
|
26
25
|
|
26
|
+
def default_membership
|
27
|
+
return nil if management?
|
28
|
+
|
29
|
+
memberships.detect { |membership| membership.blocked_at.blank? && membership.organization.present? && membership.organization.active? }
|
30
|
+
end
|
31
|
+
|
27
32
|
private
|
28
33
|
|
29
34
|
def downcase_email
|
@@ -40,11 +40,8 @@ module Maquina
|
|
40
40
|
user.management?
|
41
41
|
end
|
42
42
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
# def owner?
|
47
|
-
# record.user_id == user.id
|
48
|
-
# end
|
43
|
+
def admin?
|
44
|
+
(Maquina::Current.membership.present? && Maquina::Current.membership.admin?)
|
45
|
+
end
|
49
46
|
end
|
50
47
|
end
|
@@ -3,11 +3,11 @@
|
|
3
3
|
module Maquina
|
4
4
|
class UserPolicy < ApplicationPolicy
|
5
5
|
def index?
|
6
|
-
management?
|
6
|
+
management? || admin?
|
7
7
|
end
|
8
8
|
|
9
9
|
def new?
|
10
|
-
management?
|
10
|
+
management? || admin?
|
11
11
|
end
|
12
12
|
|
13
13
|
def create?
|
@@ -21,7 +21,11 @@ module Maquina
|
|
21
21
|
private
|
22
22
|
|
23
23
|
relation_scope do |scope|
|
24
|
-
|
24
|
+
if management?
|
25
|
+
scope.where(management: management?)
|
26
|
+
else
|
27
|
+
scope.joins(memberships: :organization).where(maquina_organizations: {id: Maquina::Current.membership.organization.id})
|
28
|
+
end
|
25
29
|
end
|
26
30
|
end
|
27
31
|
end
|
@@ -6,8 +6,7 @@
|
|
6
6
|
<%= csrf_meta_tags %>
|
7
7
|
<%= csp_meta_tag %>
|
8
8
|
|
9
|
-
<%= stylesheet_link_tag "
|
10
|
-
<%= stylesheet_link_tag "maquina/application", "data-turbo-track": "reload", media: "all" %>
|
9
|
+
<%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %>
|
11
10
|
|
12
11
|
<%= maquina_importmap_tags %>
|
13
12
|
</head>
|
@@ -6,8 +6,7 @@
|
|
6
6
|
<%= csrf_meta_tags %>
|
7
7
|
<%= csp_meta_tag %>
|
8
8
|
|
9
|
-
<%= stylesheet_link_tag "
|
10
|
-
<%= stylesheet_link_tag "maquina/application", "data-turbo-track": "reload" %>
|
9
|
+
<%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %>
|
11
10
|
|
12
11
|
<%= maquina_importmap_tags %>
|
13
12
|
</head>
|
@@ -6,7 +6,7 @@
|
|
6
6
|
<%= render Maquina::Navbar::Menu.new %>
|
7
7
|
</div>
|
8
8
|
|
9
|
-
<%= render Maquina::Navbar::Search.new(query: params[:q], url: collection_path) if action_name == "index" && respond_to?(:collection_path) && collection_path.present? %>
|
9
|
+
<%= render Maquina::Navbar::Search.new(query: params[:q], url: collection_path) if action_name == "index" && respond_to?(:collection_path) && collection_path.present? && respond_to?(:resource_class) && resource_class.searchable? %>
|
10
10
|
|
11
11
|
<%= render Maquina::Navbar::MobileButton.new %>
|
12
12
|
|
@@ -5,17 +5,21 @@ module Maquina
|
|
5
5
|
module Components
|
6
6
|
class FileComponent < ComponentBase
|
7
7
|
include Phlex::Rails::Helpers::NumberToHumanSize
|
8
|
+
include Phlex::Rails::Helpers::TokenList
|
9
|
+
|
10
|
+
delegate :main_app, to: :helpers
|
8
11
|
|
9
12
|
def template
|
10
|
-
div(**extend_control_options(
|
13
|
+
div(**extend_control_options(control_html)) do
|
11
14
|
@form.label attribute_name, class: "label #{label_css_class}"
|
12
15
|
div(class: "mt-1") do
|
13
16
|
div(data_file_target: "container") do
|
14
17
|
@form.file_field attribute_name, **extend_input_options(input_html(no_helpers: true))
|
15
|
-
img(class: "
|
16
|
-
svg(viewbox: "0 0 24 24", fill: "currentColor", aria_hidden: "true", data_action: "click->file#select", data_file_target: "placeholder") do |s|
|
18
|
+
img(class: token_list("", hidden: !attached_file?), data_file_target: "preview", src: attached_url)
|
19
|
+
svg(viewbox: "0 0 24 24", fill: "currentColor", class: token_list("", hidden: attached_file?), aria_hidden: "true", data_action: "click->file#select", data_file_target: "placeholder") do |s|
|
17
20
|
s.path(fill_rule: "evenodd", d: "M1.5 6a2.25 2.25 0 012.25-2.25h16.5A2.25 2.25 0 0122.5 6v12a2.25 2.25 0 01-2.25 2.25H3.75A2.25 2.25 0 011.5 18V6zM3 16.06V18c0 .414.336.75.75.75h16.5A.75.75 0 0021 18v-1.94l-2.69-2.689a1.5 1.5 0 00-2.12 0l-.88.879.97.97a.75.75 0 11-1.06 1.06l-5.16-5.159a1.5 1.5 0 00-2.12 0L3 16.061zm10.125-7.81a1.125 1.125 0 112.25 0 1.125 1.125 0 01-2.25 0z", clip_rule: "evenodd")
|
18
21
|
end
|
22
|
+
p(class: "text-skin-muted pb-4 pt-2 italic text-sm") { attached_info } if attached_file?
|
19
23
|
help_template
|
20
24
|
error_template
|
21
25
|
end
|
@@ -41,18 +45,47 @@ module Maquina
|
|
41
45
|
}.deep_merge(input_html)
|
42
46
|
end
|
43
47
|
|
44
|
-
def extend_control_options(
|
48
|
+
def extend_control_options(control_html)
|
45
49
|
max_size = control_html.dig(:data, :file_max_size_value) || 4_194_304
|
46
50
|
human_max_size = number_to_human_size(max_size)
|
51
|
+
file_url = attached_url
|
47
52
|
|
48
53
|
{
|
49
54
|
data: {
|
50
55
|
controller: "file",
|
51
56
|
file_max_size_value: max_size,
|
52
|
-
file_validation_message_value: t("activerecord.errors.attributes.#{@form.object.model_name.i18n_key}.#{attribute_name}.oversize", size: human_max_size, default: t("activerecord.errors.attributes.file.oversize", size: human_max_size))
|
57
|
+
file_validation_message_value: t("activerecord.errors.attributes.#{@form.object.model_name.i18n_key}.#{attribute_name}.oversize", size: human_max_size, default: t("activerecord.errors.attributes.file.oversize", size: human_max_size)),
|
58
|
+
file_url_value: file_url
|
53
59
|
}
|
54
60
|
}.deep_merge(control_html)
|
55
61
|
end
|
62
|
+
|
63
|
+
# Todo: Work on not representable files
|
64
|
+
def attached_url
|
65
|
+
return "" if !attached_file?
|
66
|
+
|
67
|
+
if attached_file.blob.representable?
|
68
|
+
puts "Representable"
|
69
|
+
main_app.url_for(attached_file.blob.variant(resize_to_limit: [1200, 768]))
|
70
|
+
else
|
71
|
+
puts "No representable"
|
72
|
+
main_app.url_for(attached_file.blob)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def attached_info
|
77
|
+
return "" if !attached_file?
|
78
|
+
|
79
|
+
"#{attached_file.blob.filename} (#{number_to_human_size(attached_file.blob.byte_size)})"
|
80
|
+
end
|
81
|
+
|
82
|
+
def attached_file
|
83
|
+
@attached_file ||= @resource.send(attribute_name)
|
84
|
+
end
|
85
|
+
|
86
|
+
def attached_file?
|
87
|
+
attached_file&.attached?
|
88
|
+
end
|
56
89
|
end
|
57
90
|
end
|
58
91
|
end
|
@@ -49,8 +49,9 @@ module Maquina
|
|
49
49
|
|
50
50
|
def form_header_template
|
51
51
|
div do
|
52
|
-
|
53
|
-
|
52
|
+
prefix = @resource.persisted? ? :edit : :new
|
53
|
+
h3(class: "text-lg font-medium leading-6 text-skin-base") { t("#{prefix}.#{resource_class.model_name.i18n_key}.title", model: model_human_name.downcase, default: t("#{prefix}.title", default: model_human_name.downcase)) }
|
54
|
+
p(class: "mt-1 text-sm text-skin-dimmed") { t("#{prefix}.#{resource_class.model_name.i18n_key}.description", model: model_human_name.downcase, default: t("#{prefix}.description", model: model_human_name.downcase)) }
|
54
55
|
end
|
55
56
|
end
|
56
57
|
|
@@ -27,7 +27,7 @@ module Maquina
|
|
27
27
|
return if policy_class.blank? || !allowed_to?(:new?, with: policy_class)
|
28
28
|
|
29
29
|
options = {}
|
30
|
-
options[:data] = {controller: "modal-open", action: "modal-open#open"} if policy_class.present? && allowed_to?(:new_modal?, with: policy_class)
|
30
|
+
options[:data] = {controller: "modal-open", modal_open_modal_outlet: ".modal", action: "modal-open#open"} if policy_class.present? && allowed_to?(:new_modal?, with: policy_class)
|
31
31
|
|
32
32
|
div(class: "mt-4 sm:mt-0 sm:ml-16 sm:flex-none") do
|
33
33
|
a(href: new_resource_path, class: "inline-flex items-center justify-center button button-accented", **options) { add_new }
|
@@ -7,9 +7,7 @@ module Maquina
|
|
7
7
|
register_element :turbo_frame
|
8
8
|
|
9
9
|
def template
|
10
|
-
div(data_controller: "modal",
|
11
|
-
data_action: " modal-open:toggle@window->modal#toggleModal close-frame-modal:toggle@window->modal#toggleModal",
|
12
|
-
data_frame_src: "") do
|
10
|
+
div(data_controller: "modal", class: "modal", data_frame_src: "") do
|
13
11
|
div(class: "fixed inset-0 z-30 hidden overflow-y-auto", aria_labelledby: "modal-title", role: "dialog",
|
14
12
|
aria_modal: "true", data_modal_target: "container") do
|
15
13
|
div(class: "flex items-end justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0") do
|
data/config/importmap.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Stimulus & Turbo
|
4
|
+
pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
|
5
|
+
pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
|
6
|
+
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
|
7
|
+
pin "stimulus-use", to: "https://ga.jspm.io/npm:stimulus-use@0.52.0/dist/index.js"
|
8
|
+
|
9
|
+
# Maquina entrypoint
|
10
|
+
pin "application", to: "maquina/application.js", preload: true
|
11
|
+
|
12
|
+
pin_all_from Maquina::Engine.root.join("app/assets/javascripts/maquina/controllers"), under: "controllers", to: "maquina/controllers"
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class CreateMaquinaOrganizations < ActiveRecord::Migration[7.0]
|
2
|
+
def change
|
3
|
+
create_table :maquina_organizations do |t|
|
4
|
+
t.string :name
|
5
|
+
t.boolean :active, default: true
|
6
|
+
t.references :maquina_plan, null: true, foreign_key: true
|
7
|
+
|
8
|
+
t.timestamps
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class CreateMaquinaMemberships < ActiveRecord::Migration[7.0]
|
2
|
+
def change
|
3
|
+
create_enum :maquina_role, %w[admin member]
|
4
|
+
|
5
|
+
create_table :maquina_memberships do |t|
|
6
|
+
t.references :maquina_organization, null: false, foreign_key: true
|
7
|
+
t.references :maquina_user, null: false, foreign_key: true
|
8
|
+
t.boolean :owner, default: false
|
9
|
+
t.enum :role, enum_type: :maquina_role, default: "member", null: false
|
10
|
+
t.timestamp :blocked_at
|
11
|
+
|
12
|
+
t.timestamps
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
Description:
|
2
|
+
It will create a custom TailwindCSS configuration file for Rails applications. The custom configuration file is
|
3
|
+
auto-generated that allows to include Engines files.
|
4
|
+
|
5
|
+
Add the file config/tailwind.config.js to the .gitignore file, and if you need to modify it, modify the template
|
6
|
+
file included in the generator.
|
7
|
+
|
8
|
+
Example:
|
9
|
+
bin/rails generate maquina:tailwind_config
|
10
|
+
|
11
|
+
This will create:
|
12
|
+
lib/tailwind.rake
|
13
|
+
lib/generators/tailwind_config/tailwind_config_generator.rb
|
14
|
+
lib/generators/tailwind_config/templates/tailwind.config.js.tt
|
@@ -2,10 +2,24 @@
|
|
2
2
|
|
3
3
|
module Maquina
|
4
4
|
class TailwindConfigGenerator < Rails::Generators::Base
|
5
|
-
source_root File.expand_path("
|
5
|
+
source_root File.expand_path("templates", __dir__)
|
6
6
|
|
7
|
-
def
|
8
|
-
|
7
|
+
def create_generator
|
8
|
+
tailwind_config_file = "lib/generators/tailwind_config/templates/config/tailwind.config.js.tt"
|
9
|
+
|
10
|
+
template "lib/tasks/tailwind.rake"
|
11
|
+
template "lib/generators/tailwind_config/tailwind_config_generator.rb"
|
12
|
+
|
13
|
+
# Seems that template can't handle .tt.tt extensions correctly
|
14
|
+
copy_file tailwind_config_file, tailwind_config_file
|
15
|
+
end
|
16
|
+
|
17
|
+
def configure_css
|
18
|
+
copy_file "app/assets/stylesheets/maquina.css"
|
19
|
+
|
20
|
+
inject_into_file "app/assets/stylesheets/application.tailwind.css", after: /base"?;/ do
|
21
|
+
%(\n@import "./maquina.css";)
|
22
|
+
end
|
9
23
|
end
|
10
24
|
end
|
11
25
|
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
:root {
|
2
|
+
--color-base: 31 27 54;
|
3
|
+
--color-accented: 37 99 235;
|
4
|
+
--color-inverted: 255 255 255;
|
5
|
+
--color-accented-hover: 59 130 246;
|
6
|
+
--color-muted: 55 65 81;
|
7
|
+
--color-dimmed: 75 85 99;
|
8
|
+
--color-error: 220 38 38;
|
9
|
+
|
10
|
+
--color-border-base: 209 213 219;
|
11
|
+
--color-border-accented: 37 99 235;
|
12
|
+
}
|
13
|
+
|
14
|
+
.label {
|
15
|
+
@apply text-sm font-medium text-skin-muted;
|
16
|
+
}
|
17
|
+
|
18
|
+
.input {
|
19
|
+
@apply appearance-none rounded-md border border-skin-base px-3 py-2 placeholder-gray-400 shadow-sm focus:border-skin-accented focus:outline-none focus:ring-skin-accented sm:text-sm;
|
20
|
+
}
|
21
|
+
|
22
|
+
.link {
|
23
|
+
@apply font-medium text-skin-accented hover:text-skin-accented-hover;
|
24
|
+
}
|
25
|
+
|
26
|
+
.check {
|
27
|
+
@apply border-skin-base h-4 w-4 text-skin-accented focus:ring-skin-accented rounded;
|
28
|
+
}
|
29
|
+
|
30
|
+
.button {
|
31
|
+
@apply inline-flex justify-center py-2 px-4 border border-skin-base rounded-md shadow-sm text-sm font-medium text-skin-muted bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-skin-accented;
|
32
|
+
}
|
33
|
+
|
34
|
+
.control-error .label {
|
35
|
+
@apply text-skin-error;
|
36
|
+
}
|
37
|
+
|
38
|
+
.control-error .input {
|
39
|
+
@apply border-skin-error focus:border-skin-error focus:ring-skin-error;
|
40
|
+
}
|
41
|
+
|
42
|
+
.help {
|
43
|
+
@apply mt-2 text-sm text-skin-dimmed;
|
44
|
+
}
|
45
|
+
|
46
|
+
.help-error {
|
47
|
+
@apply text-skin-error;
|
48
|
+
}
|
49
|
+
|
50
|
+
.button[disabled] {
|
51
|
+
@apply bg-gray-50 text-skin-dimmed cursor-default;
|
52
|
+
}
|
53
|
+
|
54
|
+
.button-accented {
|
55
|
+
@apply rounded-md border border-transparent bg-skin-accented py-2 px-4 text-sm font-medium text-skin-inverted shadow-sm hover:bg-skin-accented-hover focus:outline-none focus:ring-2 focus:ring-skin-accented focus:ring-offset-2;
|
56
|
+
}
|
57
|
+
|
58
|
+
input[type="search"] {
|
59
|
+
@apply block w-full rounded-md border border-skin-base pl-10 leading-5 placeholder-gray-400 focus:placeholder-gray-300 focus:outline-none focus:ring-1 focus:ring-skin-accented focus:border-skin-accented sm:text-sm;
|
60
|
+
}
|
61
|
+
|
62
|
+
.desktop-menu {
|
63
|
+
@apply hidden lg:ml-6 lg:flex lg:space-x-8;
|
64
|
+
}
|
65
|
+
|
66
|
+
.desktop-menu-item {
|
67
|
+
@apply font-sans uppercase border-transparent text-skin-dimmed underline-offset-4 decoration-2 hover:underline hover:decoration-skin-accented hover:text-skin-muted inline-flex items-center px-1 pt-1 text-sm font-medium;
|
68
|
+
}
|
69
|
+
|
70
|
+
.desktop-menu-item__active {
|
71
|
+
@apply decoration-skin-accented underline text-skin-base;
|
72
|
+
}
|
73
|
+
|
74
|
+
.mobile-menu {
|
75
|
+
@apply pt-2 pb-3 space-y-1;
|
76
|
+
}
|
77
|
+
|
78
|
+
.mobile-menu-item {
|
79
|
+
@apply font-sans border-transparent text-skin-dimmed hover:bg-gray-200 hover:border-gray-300 hover:text-skin-muted block pl-3 pr-4 py-2 border-l-4 text-base font-medium;
|
80
|
+
}
|
81
|
+
|
82
|
+
.mobile-menu-item__active {
|
83
|
+
@apply bg-skin-accented-hover border-skin-accented text-skin-accented hover:text-skin-accented hover:bg-skin-accented-hover hover:border-skin-accented;
|
84
|
+
}
|
85
|
+
|
86
|
+
.desktop-profile-menu-item {
|
87
|
+
@apply block w-full text-left font-sans px-4 py-2 text-sm text-skin-dimmed hover:bg-gray-200 hover:text-skin-muted;
|
88
|
+
}
|
89
|
+
|
90
|
+
.mobile-profile-menu-item {
|
91
|
+
@apply block font-sans px-4 py-2 text-base font-medium text-skin-dimmed hover:text-skin-muted hover:bg-gray-200;
|
92
|
+
}
|
93
|
+
|
94
|
+
.mobile-button {
|
95
|
+
@apply inline-flex items-center justify-center p-2 rounded-md text-skin-dimmed hover:text-skin-muted hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-skin-accented;
|
96
|
+
}
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class TailwindConfigGenerator < Rails::Generators::Base
|
4
|
+
source_root File.expand_path("templates", __dir__)
|
5
|
+
|
6
|
+
def create_tailwind_config_file
|
7
|
+
@engines_paths = Maquina.configuration.tailwind_content
|
8
|
+
|
9
|
+
template "config/tailwind.config.js"
|
10
|
+
end
|
11
|
+
end
|
@@ -2,6 +2,10 @@ const defaultTheme = require('tailwindcss/defaultTheme')
|
|
2
2
|
|
3
3
|
module.exports = {
|
4
4
|
content: [
|
5
|
+
'public/*.html',
|
6
|
+
'app/helpers/**/*.rb',
|
7
|
+
'app/javascript/**/*.js',
|
8
|
+
'app/views/**/*.{erb,haml,html,slim}',
|
5
9
|
<%= Maquina.configuration.tailwind_content.map{|path| "'#{path}'"}.join(",\n") %>
|
6
10
|
],
|
7
11
|
theme: {
|