maquina 0.1.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: {
|