maquina 0.5.2 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/Gemfile +2 -0
- data/Gemfile.lock +3 -35
- data/app/assets/javascripts/controllers/alert_controller.js +22 -0
- data/app/assets/stylesheets/maquina/application.tailwind.css +4 -101
- data/app/assets/stylesheets/maquina.css +114 -0
- data/app/controllers/concerns/maquina/authenticate.rb +29 -1
- data/app/controllers/concerns/maquina/index.rb +81 -2
- data/app/controllers/concerns/maquina/resourceful.rb +95 -9
- data/app/controllers/maquina/application_controller.rb +1 -1
- data/app/controllers/maquina/dashboard_controller.rb +10 -5
- data/app/controllers/maquina/invitations_controller.rb +0 -2
- data/app/controllers/maquina/plans_controller.rb +13 -23
- data/app/controllers/maquina/sessions_controller.rb +1 -1
- data/app/controllers/maquina/users_controller.rb +0 -2
- data/app/helpers/maquina/application_helper.rb +0 -8
- data/app/helpers/maquina/navbar_menu_helper.rb +1 -1
- data/app/models/concerns/maquina/blockeable.rb +64 -0
- data/app/models/concerns/maquina/organization_scoped.rb +26 -0
- data/app/models/concerns/maquina/retain_passwords.rb +44 -0
- data/app/models/concerns/maquina/sqlite_search.rb +92 -0
- data/app/models/concerns/maquina/user_scoped.rb +26 -0
- data/app/models/maquina/active_session.rb +40 -0
- data/app/models/maquina/current.rb +39 -2
- data/app/models/maquina/invitation.rb +28 -0
- data/app/models/maquina/membership.rb +23 -1
- data/app/models/maquina/organization.rb +19 -2
- data/app/models/maquina/plan.rb +26 -6
- data/app/models/maquina/used_password.rb +30 -0
- data/app/models/maquina/user.rb +50 -8
- data/app/policies/maquina/application_policy.rb +1 -1
- data/app/policies/maquina/dashboard_policy.rb +7 -0
- data/app/views/layouts/maquina/application.html.erb +2 -2
- data/app/views/layouts/maquina/sessions.html.erb +1 -1
- data/app/views/maquina/application/_navbar.html.erb +8 -3
- data/app/views/maquina/application/components/checkbox_component.rb +3 -3
- data/app/views/maquina/application/components/component_base.rb +3 -2
- data/app/views/maquina/application/edit.html.erb +10 -7
- data/app/views/maquina/application/filters.rb +118 -0
- data/app/views/maquina/application/index.html.erb +6 -3
- data/app/views/maquina/application/index_header.rb +5 -2
- data/app/views/maquina/application/index_table.rb +71 -6
- data/app/views/maquina/application/index_tools.rb +17 -0
- data/app/views/maquina/application/new.html.erb +10 -7
- data/app/views/maquina/application/search.rb +42 -0
- data/app/views/maquina/application/sessions_header.rb +3 -9
- data/app/views/maquina/dashboard/index.html.erb +4 -0
- data/app/views/maquina/dashboard/stats.rb +35 -0
- data/app/views/maquina/dashboard/tasks.rb +124 -0
- data/app/views/maquina/first_runs/form.rb +0 -2
- data/app/views/maquina/first_runs/show.html.erb +4 -1
- data/app/views/maquina/navbar/title.rb +4 -2
- data/config/importmap.rb +1 -13
- data/config/locales/flash.en.yml +6 -0
- data/config/locales/flash.es.yml +6 -0
- data/config/locales/forms.en.yml +33 -4
- data/config/locales/forms.es.yml +22 -11
- data/config/locales/models.en.yml +10 -0
- data/config/locales/models.es.yml +10 -0
- data/config/locales/views.en.yml +33 -5
- data/config/locales/views.es.yml +28 -10
- data/config/routes.rb +1 -0
- data/db/migrate/20221109010726_create_maquina_plans.rb +1 -1
- data/db/migrate/20221113000409_create_maquina_users.rb +1 -1
- data/db/migrate/20221113020108_create_maquina_used_passwords.rb +1 -1
- data/db/migrate/20221115223414_create_maquina_active_sessions.rb +1 -3
- data/db/migrate/20230201203922_create_maquina_invitations.rb +1 -1
- data/db/migrate/20230829183530_create_maquina_organizations.rb +1 -1
- data/db/migrate/20230829192656_create_maquina_memberships.rb +2 -4
- data/db/migrate/20241109191405_add_counter_cache_to_plans.rb +5 -0
- data/lib/generators/maquina/install_generator.rb +67 -1
- data/lib/generators/maquina/tailwind_config/templates/lib/generators/tailwind_config/templates/config/tailwind.config.js.tt +9 -5
- data/lib/generators/maquina/tailwind_config/templates/lib/tasks/tailwind.rake.tt +2 -0
- data/lib/generators/maquina/templates/config/initializers/maquina.rb.tt +2 -1
- data/lib/maquina/engine.rb +6 -3
- data/lib/maquina/version.rb +1 -1
- data/lib/maquina.rb +2 -9
- metadata +20 -76
- data/app/assets/javascripts/maquina/application.js +0 -4
- data/app/assets/javascripts/maquina/controllers/alert_controller.js +0 -29
- data/app/assets/javascripts/maquina/controllers/application.js +0 -9
- data/app/assets/javascripts/maquina/controllers/index.js +0 -11
- data/app/models/concerns/maquina/authenticate_by.rb +0 -33
- data/app/views/maquina/navbar/search.rb +0 -40
- data/lib/generators/maquina/install_templates/install_templates_generator.rb +0 -31
- /data/app/assets/javascripts/{maquina/controllers → controllers}/backdrop_controller.js +0 -0
- /data/app/assets/javascripts/{maquina/controllers → controllers}/file_controller.js +0 -0
- /data/app/assets/javascripts/{maquina/controllers → controllers}/mobile_menu_controller.js +0 -0
- /data/app/assets/javascripts/{maquina/controllers → controllers}/modal_controller.js +0 -0
- /data/app/assets/javascripts/{maquina/controllers → controllers}/modal_open_controller.js +0 -0
- /data/app/assets/javascripts/{maquina/controllers → controllers}/popup_menu_controller.js +0 -0
- /data/app/assets/javascripts/{maquina/controllers → controllers}/submit_form_controller.js +0 -0
@@ -1,10 +1,13 @@
|
|
1
1
|
<div class="px-4 sm:px-6 lg:px-8">
|
2
2
|
<%= render Maquina::Application::IndexHeader.new(params[:q]) %>
|
3
3
|
|
4
|
+
<%= render Maquina::Application::IndexTools.new %>
|
5
|
+
|
4
6
|
<%= render Maquina::Application::IndexTable.new(
|
5
|
-
|
6
|
-
|
7
|
-
|
7
|
+
collection: collection,
|
8
|
+
pagination: @pagination,
|
9
|
+
list_attributes: list_attributes,
|
10
|
+
) %>
|
8
11
|
</div>
|
9
12
|
|
10
13
|
<%= render Maquina::Application::IndexModal.new %>
|
@@ -13,9 +13,8 @@ module Maquina
|
|
13
13
|
def view_template
|
14
14
|
div(class: "sm:flex sm:items-center") do
|
15
15
|
div(class: "sm:flex-auto") do
|
16
|
-
h1(class: "text-xl font-semibold text-skin-base") {
|
16
|
+
h1(class: "text-xl font-semibold text-skin-base") { model_title }
|
17
17
|
p(class: "mt-2 text-sm text-skin-muted") { description }
|
18
|
-
p(class: "mt-4 text-skin-base") { unsafe_raw t("index.search", search: @filter) } if @filter.present?
|
19
18
|
end
|
20
19
|
add_new_template
|
21
20
|
end
|
@@ -23,6 +22,10 @@ module Maquina
|
|
23
22
|
|
24
23
|
private
|
25
24
|
|
25
|
+
def model_title
|
26
|
+
t("index.#{resource_class.model_name.i18n_key}.title", model: model_human_name(plural: true).downcase, default: model_human_name(plural: true))
|
27
|
+
end
|
28
|
+
|
26
29
|
def add_new_template
|
27
30
|
return if policy_class.blank? || !allowed_to?(:new?, with: policy_class)
|
28
31
|
|
@@ -4,8 +4,10 @@ module Maquina
|
|
4
4
|
module Application
|
5
5
|
class IndexTable < Phlex::HTML
|
6
6
|
include ApplicationView
|
7
|
+
include Phlex::Rails::Helpers::LinkTo
|
7
8
|
|
8
|
-
delegate :humanized_money_with_symbol, :edit_resource_path, :resource_path,
|
9
|
+
delegate :humanized_money_with_symbol, :edit_resource_path, :resource_path, :collection_path, :params, :sortable,
|
10
|
+
to: :helpers
|
9
11
|
|
10
12
|
def initialize(collection: nil, pagination: nil, list_attributes: nil)
|
11
13
|
@collection = collection
|
@@ -39,8 +41,8 @@ module Maquina
|
|
39
41
|
end
|
40
42
|
|
41
43
|
def table_headers_template
|
42
|
-
base_css = "px-
|
43
|
-
first_css = "py-
|
44
|
+
base_css = "px-1 py-3 text-left text-sm font-medium text-skin-base"
|
45
|
+
first_css = "py-1 py-3 text-left text-sm font-medium text-skin-base sm:pl-6"
|
44
46
|
|
45
47
|
@list_attributes.each_with_index do |attribute, index|
|
46
48
|
current_css = if index == 0
|
@@ -49,7 +51,21 @@ module Maquina
|
|
49
51
|
base_css
|
50
52
|
end
|
51
53
|
|
52
|
-
th(scope: :col, class: current_css)
|
54
|
+
th(scope: :col, class: current_css) do
|
55
|
+
if sortable.include?(attribute)
|
56
|
+
link_to(collection_path(sort_link_params(attribute)), class: "inline-flex items-center justify-center gap-2 whitespace-nowrap hover:bg-gray-100 hover:cursor-pointer p-2 rounded-sm") do
|
57
|
+
span { attribute_human_name(attribute) }
|
58
|
+
unsafe_raw case sort_status(attribute)
|
59
|
+
when :asc then asc_sort_icon
|
60
|
+
when :desc then desc_sort_icon
|
61
|
+
else
|
62
|
+
no_sort_icon
|
63
|
+
end
|
64
|
+
end
|
65
|
+
else
|
66
|
+
span { attribute_human_name(attribute) }
|
67
|
+
end
|
68
|
+
end
|
53
69
|
end
|
54
70
|
end
|
55
71
|
|
@@ -58,7 +74,7 @@ module Maquina
|
|
58
74
|
|
59
75
|
tr do
|
60
76
|
th(scope: "col", colspan: col_span, class: "whitespace-nowrap px-3 py-4 text-sm text-skin-dimmed") do
|
61
|
-
t("index.no_items", model: model_human_name(plural: true))
|
77
|
+
t("index.no_items", model: model_human_name(plural: true).downcase)
|
62
78
|
end
|
63
79
|
end
|
64
80
|
end
|
@@ -67,7 +83,7 @@ module Maquina
|
|
67
83
|
base_css = "whitespace-nowrap px-3 py-4 text-sm text-skin-muted"
|
68
84
|
first_css = "whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-skin-base sm:pl-6"
|
69
85
|
|
70
|
-
tr do
|
86
|
+
tr(class: "hover:bg-gray-50") do
|
71
87
|
@list_attributes.each_with_index do |attribute, index|
|
72
88
|
td(class: (index == 0) ? first_css : base_css, scope: "col") { attribute_to_link(item, attribute, attribute_value(item, attribute)) }
|
73
89
|
end
|
@@ -122,6 +138,55 @@ module Maquina
|
|
122
138
|
|
123
139
|
link_to(value, resource_path(item), class: "link", data: {"turbo-frame": "_top"})
|
124
140
|
end
|
141
|
+
|
142
|
+
def sort_link_params(attribute)
|
143
|
+
current_params = params.dup.to_unsafe_h.except(:controller, :action)
|
144
|
+
current_order = current_params[:order]
|
145
|
+
current_dir = current_params[:dir]
|
146
|
+
|
147
|
+
if current_order != attribute.to_s
|
148
|
+
current_params[:order] = attribute
|
149
|
+
current_params[:dir] = :desc
|
150
|
+
elsif current_dir == "desc"
|
151
|
+
current_params[:dir] = :asc
|
152
|
+
elsif current_dir == "asc"
|
153
|
+
current_params.except!(:order, :dir)
|
154
|
+
else
|
155
|
+
current_params[:order] = attribute
|
156
|
+
current_params[:dir] = :desc
|
157
|
+
end
|
158
|
+
|
159
|
+
current_params
|
160
|
+
end
|
161
|
+
|
162
|
+
def sort_status(attribute)
|
163
|
+
return nil unless params[:order] == attribute.to_s
|
164
|
+
(params[:dir] == "asc") ? :asc : :desc
|
165
|
+
end
|
166
|
+
|
167
|
+
def no_sort_icon
|
168
|
+
<<~SVG
|
169
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-auto text-skin-dimmed">
|
170
|
+
<path d="m7 15 5 5 5-5"></path><path d="m7 9 5-5 5 5"></path>
|
171
|
+
</svg>
|
172
|
+
SVG
|
173
|
+
end
|
174
|
+
|
175
|
+
def desc_sort_icon
|
176
|
+
<<~SVG
|
177
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-auto text-skin-dimmed">
|
178
|
+
<path d="M12 5v14"></path><path d="m19 12-7 7-7-7"></path>
|
179
|
+
</svg>
|
180
|
+
SVG
|
181
|
+
end
|
182
|
+
|
183
|
+
def asc_sort_icon
|
184
|
+
<<~SVG
|
185
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-auto text-skin-dimmed">
|
186
|
+
<path d="m5 12 7-7 7 7"></path><path d="M12 19V5"></path>
|
187
|
+
</svg>
|
188
|
+
SVG
|
189
|
+
end
|
125
190
|
end
|
126
191
|
end
|
127
192
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Maquina
|
2
|
+
module Application
|
3
|
+
class IndexTools < Phlex::HTML
|
4
|
+
include ApplicationView
|
5
|
+
delegate :resource_class, :filters, to: :helpers
|
6
|
+
|
7
|
+
def view_template
|
8
|
+
div(class: "flex items-center justify-between mt-8") do
|
9
|
+
div(class: "flex flex-1 items-center space-x-2") do
|
10
|
+
render Maquina::Application::Search.new if resource_class.searchable?
|
11
|
+
render Maquina::Application::Filters.new if filters.any?
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -1,9 +1,12 @@
|
|
1
|
-
<div
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
1
|
+
<div
|
2
|
+
class="
|
3
|
+
bg-white mx-auto max-w-4xl py-12 px-4 sm:px-6 lg:px-8 border border-gray-200
|
4
|
+
rounded-lg shadow-sm
|
5
|
+
"
|
6
|
+
>
|
7
|
+
<div class="mx-auto max-w-3xl">
|
8
|
+
<%= turbo_frame_tag class_to_form_frame(resource_class) do %>
|
9
|
+
<%= render Maquina::Application::New.new(resource: resource) %>
|
10
|
+
<% end %>
|
8
11
|
</div>
|
9
12
|
</div>
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Maquina
|
4
|
+
module Application
|
5
|
+
class Search < Phlex::HTML
|
6
|
+
include ApplicationView
|
7
|
+
include Phlex::Rails::Helpers::FormWith
|
8
|
+
|
9
|
+
delegate :t, :request, :collection_path, :params, to: :helpers
|
10
|
+
|
11
|
+
def view_template
|
12
|
+
current_params = request.params.except(:q, :page, :controller, :action)
|
13
|
+
current_params.reject! { |key, value| value.blank? }
|
14
|
+
query = params[:q]
|
15
|
+
|
16
|
+
form_with(url: @url, method: :get, data: {controller: "submit-form"}, class: "pr-3") do |form|
|
17
|
+
current_params.each do |key, value|
|
18
|
+
form.hidden_field key, value: value
|
19
|
+
end
|
20
|
+
form.label :search, t("search.search"), class: "sr-only"
|
21
|
+
|
22
|
+
div class: "relative" do
|
23
|
+
div class: "absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none" do
|
24
|
+
unsafe_raw search_icon
|
25
|
+
end
|
26
|
+
form.text_field :q, value: query, placeholder: t("search.search"), class: "block w-full font-sans", maxlength: 15, type: :search, data: {action: "input->submit-form#clear"}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def search_icon
|
34
|
+
<<~SVG
|
35
|
+
<svg class="h-5 w-5 text-skin-dimmed" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true">
|
36
|
+
<path d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"></path>
|
37
|
+
</svg>
|
38
|
+
SVG
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -7,7 +7,7 @@ module Maquina
|
|
7
7
|
include Phlex::DeferredRender
|
8
8
|
include Phlex::Rails::Helpers::Translate
|
9
9
|
|
10
|
-
def initialize(brand_icon:, translation_key: "maquina.
|
10
|
+
def initialize(brand_icon:, translation_key: "maquina.sessions.new")
|
11
11
|
@brand_icon = brand_icon
|
12
12
|
@translation_key = translation_key
|
13
13
|
end
|
@@ -17,17 +17,11 @@ module Maquina
|
|
17
17
|
image_tag(@brand_icon, class: "mx-auto h-12 w-auto", alt: t("application_name"))
|
18
18
|
h2(class: "mt-6 text-center text-3xl font-bold tracking-tight text-skin-base") { t("#{@translation_key}.title") }
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
yield_content(&@description)
|
23
|
-
end
|
20
|
+
p(class: "mt-2 text-center text-sm text-skin-dimmed") do
|
21
|
+
t("#{@translation_key}.subtitle", default: "")
|
24
22
|
end
|
25
23
|
end
|
26
24
|
end
|
27
|
-
|
28
|
-
def description(&block)
|
29
|
-
@description = block
|
30
|
-
end
|
31
25
|
end
|
32
26
|
end
|
33
27
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Maquina
|
2
|
+
module Dashboard
|
3
|
+
class Stats < Phlex::HTML
|
4
|
+
include Phlex::Rails::Helpers::Translate
|
5
|
+
|
6
|
+
def initialize(stats:)
|
7
|
+
@stats = stats
|
8
|
+
end
|
9
|
+
|
10
|
+
def view_template
|
11
|
+
div(class: "mx-auto max-w-lg") do
|
12
|
+
h3(class: "text-base font-semibold text-skin-base") { t("maquina.dashboard.index.stats.title") }
|
13
|
+
|
14
|
+
dl(class: "mt-5 grid grid-cols-2 gap-5 sm:grid-cols-#{@stats.length}") do
|
15
|
+
@stats.each do |stat|
|
16
|
+
stats_card(
|
17
|
+
label: stat[:label],
|
18
|
+
value: stat[:value]
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def stats_card(label:, value:)
|
28
|
+
div(class: "overflow-hidden rounded-lg bg-white px-4 py-5 shadow sm:p-6") do
|
29
|
+
dt(class: "truncate text-sm font-medium text-skin-dimmed") { label }
|
30
|
+
dd(class: "mt-1 text-3xl font-semibold tracking-tight text-skin-base") { value }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module Maquina
|
2
|
+
module Dashboard
|
3
|
+
class Tasks < Phlex::HTML
|
4
|
+
include Maquina::ApplicationView
|
5
|
+
|
6
|
+
def initialize(translation_key: "maquina.dashboard.index.tasks")
|
7
|
+
@translation_key = translation_key
|
8
|
+
end
|
9
|
+
|
10
|
+
def view_template
|
11
|
+
div(class: "mx-auto max-w-lg mt-12") do
|
12
|
+
h2(class: "text-base font-semibold text-skin-base") do
|
13
|
+
t("#{@translation_key}.title")
|
14
|
+
end
|
15
|
+
|
16
|
+
p(class: "mt-1 text-sm text-skin-dimmed") do
|
17
|
+
t("#{@translation_key}.subtitle")
|
18
|
+
end
|
19
|
+
|
20
|
+
ul(
|
21
|
+
role: "list",
|
22
|
+
class: "mt-6 divide-y divide-gray-200 border-b border-t border-gray-200"
|
23
|
+
) do
|
24
|
+
# Create Plan Task
|
25
|
+
task_item(
|
26
|
+
title: t("#{@translation_key}.create_plan.title"),
|
27
|
+
description: t("#{@translation_key}.create_plan.description"),
|
28
|
+
color: "bg-skin-accented",
|
29
|
+
path: plans_path,
|
30
|
+
icon: create_plan_icon
|
31
|
+
)
|
32
|
+
|
33
|
+
# Manage Users Task
|
34
|
+
task_item(
|
35
|
+
title: t("#{@translation_key}.manage_users.title"),
|
36
|
+
description: t("#{@translation_key}.manage_users.description"),
|
37
|
+
color: "bg-skin-alternate",
|
38
|
+
path: users_path,
|
39
|
+
icon: manage_users_icon
|
40
|
+
)
|
41
|
+
|
42
|
+
# Organization Settings Task
|
43
|
+
task_item(
|
44
|
+
title: t("#{@translation_key}.organization_settings.title"),
|
45
|
+
description: t("#{@translation_key}.organization_settings.description"),
|
46
|
+
color: "bg-skin-alternate-2",
|
47
|
+
path: "#",
|
48
|
+
icon: organization_settings_icon
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
# TODO: Uncomment or remove this code later
|
53
|
+
# div(class: "mt-6 flex") do
|
54
|
+
# a(href: "#", class: "text-sm font-medium text-indigo-600 hover:text-indigo-500") do
|
55
|
+
# t("#{@translation_key}.view_all")
|
56
|
+
# span(aria_hidden: "true") { " →" }
|
57
|
+
# end
|
58
|
+
# end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def task_item(title:, description:, color:, path:, icon:)
|
65
|
+
li do
|
66
|
+
div(class: "group relative flex items-start space-x-3 py-4 hover:bg-white/75") do
|
67
|
+
div(class: "shrink-0") do
|
68
|
+
span(class: "inline-flex h-10 w-10 items-center justify-center rounded-lg #{color}") do
|
69
|
+
unsafe_raw icon
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
div(class: "min-w-0 flex-1") do
|
74
|
+
div(class: "text-sm font-medium text-skin-base") do
|
75
|
+
a(href: path) do
|
76
|
+
span(class: "absolute inset-0", aria_hidden: "true")
|
77
|
+
span { title }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
p(class: "text-sm text-skin-dimmed") { description }
|
81
|
+
end
|
82
|
+
|
83
|
+
div(class: "shrink-0 self-center") do
|
84
|
+
unsafe_raw chevron_icon
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def create_plan_icon
|
91
|
+
<<~SVG
|
92
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6 text-white">
|
93
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 16.875h3.375m0 0h3.375m-3.375 0V13.5m0 3.375v3.375M6 10.5h2.25a2.25 2.25 0 0 0 2.25-2.25V6a2.25 2.25 0 0 0-2.25-2.25H6A2.25 2.25 0 0 0 3.75 6v2.25A2.25 2.25 0 0 0 6 10.5Zm0 9.75h2.25A2.25 2.25 0 0 0 10.5 18v-2.25a2.25 2.25 0 0 0-2.25-2.25H6a2.25 2.25 0 0 0-2.25 2.25V18A2.25 2.25 0 0 0 6 20.25Zm9.75-9.75H18a2.25 2.25 0 0 0 2.25-2.25V6A2.25 2.25 0 0 0 18 3.75h-2.25A2.25 2.25 0 0 0 13.5 6v2.25a2.25 2.25 0 0 0 2.25 2.25Z" />
|
94
|
+
</svg>
|
95
|
+
SVG
|
96
|
+
end
|
97
|
+
|
98
|
+
def manage_users_icon
|
99
|
+
<<~SVG
|
100
|
+
<svg class="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
101
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z" />
|
102
|
+
</svg>
|
103
|
+
SVG
|
104
|
+
end
|
105
|
+
|
106
|
+
def organization_settings_icon
|
107
|
+
<<~SVG
|
108
|
+
<svg class="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
109
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z" />
|
110
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
111
|
+
</svg>
|
112
|
+
SVG
|
113
|
+
end
|
114
|
+
|
115
|
+
def chevron_icon
|
116
|
+
<<~SVG
|
117
|
+
<svg class="h-5 w-5 text-gray-400 group-hover:text-skin-dimmed" viewBox="0 0 20 20" fill="currentColor">
|
118
|
+
<path fill-rule="evenodd" d="M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" />
|
119
|
+
</svg>
|
120
|
+
SVG
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -6,13 +6,11 @@ module Maquina
|
|
6
6
|
|
7
7
|
def initialize(resource)
|
8
8
|
@resource = resource
|
9
|
-
@scope = "first_runs"
|
10
9
|
end
|
11
10
|
|
12
11
|
def view_template
|
13
12
|
div(class: "mt-8 sm:mx-auto sm:w-full sm:max-w-md") do
|
14
13
|
div(class: "bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10") do
|
15
|
-
p(class: "text-center text-skin-muted") { t("maquina.application.first_runs_header.description") }
|
16
14
|
form_with(model: @resource, url: first_run_path, method: :post, class: "space-y-6") do |form|
|
17
15
|
text_field(form, field_name: :email, required: true)
|
18
16
|
password_field(form, field_name: :password, required: true)
|
@@ -1,5 +1,8 @@
|
|
1
1
|
<div class="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
2
2
|
|
3
|
-
<%= render Maquina::Application::SessionsHeader.new(
|
3
|
+
<%= render Maquina::Application::SessionsHeader.new(
|
4
|
+
brand_icon: brand_icon,
|
5
|
+
translation_key: "maquina.first_runs.show",
|
6
|
+
) %>
|
4
7
|
<%= render Maquina::FirstRuns::Form.new(@user) %>
|
5
8
|
</div>
|
@@ -12,8 +12,10 @@ module Maquina
|
|
12
12
|
def view_template
|
13
13
|
div class: "flex-shrink-0 flex items-center" do
|
14
14
|
if @brand_icon.present?
|
15
|
-
|
16
|
-
|
15
|
+
a(href: root_path) do
|
16
|
+
img(src: @brand_icon, class: "hidden lg:block mx-auto h-8 w-auto")
|
17
|
+
img(src: @brand_icon, class: "block lg:hidden mx-auto h-8 w-auto")
|
18
|
+
end
|
17
19
|
end
|
18
20
|
end
|
19
21
|
end
|
data/config/importmap.rb
CHANGED
@@ -1,13 +1 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
pin "application" # , to: "maquina/application.js", preload: true
|
4
|
-
|
5
|
-
# Stimulus & Turbo
|
6
|
-
pin "@hotwired/turbo-rails", to: "turbo.min.js"
|
7
|
-
pin "@hotwired/stimulus", to: "@hotwired--stimulus.js" # @3.2.2
|
8
|
-
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
|
9
|
-
pin "stimulus-use" # , to: "https://ga.jspm.io/npm:stimulus-use@0.52.2/dist/index.js"
|
10
|
-
|
11
|
-
# Maquina entrypoint
|
12
|
-
|
13
|
-
pin_all_from Maquina::Engine.root.join("app/assets/javascripts/maquina/controllers"), under: "controllers" # , to: "maquina/controllers"
|
1
|
+
pin_all_from File.expand_path("../app/assets/javascripts/controllers", __dir__), under: "controllers", preload: false
|
data/config/locales/flash.en.yml
CHANGED
data/config/locales/flash.es.yml
CHANGED
@@ -30,6 +30,12 @@ es:
|
|
30
30
|
title: Invitación enviada
|
31
31
|
description: La inivitación para %{email} expira en 3 días
|
32
32
|
|
33
|
+
first_runs:
|
34
|
+
create:
|
35
|
+
notice:
|
36
|
+
title: Cuenta de administrador creada
|
37
|
+
description: Bienvenido a maquina!
|
38
|
+
|
33
39
|
sessions:
|
34
40
|
create:
|
35
41
|
notice:
|
data/config/locales/forms.en.yml
CHANGED
@@ -1,11 +1,21 @@
|
|
1
1
|
en:
|
2
2
|
new:
|
3
|
+
title: New %{model}
|
4
|
+
description: Complete the form to create a new %{model}
|
3
5
|
maquina/plan:
|
4
|
-
title:
|
6
|
+
title: Create subscription plan
|
7
|
+
description: Set up a new subscription plan with pricing and features
|
5
8
|
maquina/invitation:
|
6
9
|
title: Invite user to collaborate
|
7
10
|
description: User will receive an invitation to join this application in the provided email.
|
8
11
|
|
12
|
+
edit:
|
13
|
+
title: Edit %{model}
|
14
|
+
description: Modify the %{model} details
|
15
|
+
maquina/plan:
|
16
|
+
title: Edit subscription plan
|
17
|
+
description: Modify existing subscription plan details and configurations
|
18
|
+
|
9
19
|
form:
|
10
20
|
sessions:
|
11
21
|
email: Email address
|
@@ -21,12 +31,12 @@ en:
|
|
21
31
|
email: user@mail.com
|
22
32
|
password: Current password
|
23
33
|
|
24
|
-
maquina/plan:
|
25
|
-
name: Basic plan
|
26
|
-
|
27
34
|
maquina/invitation:
|
28
35
|
email: Enter user's email
|
29
36
|
|
37
|
+
first_runs:
|
38
|
+
email: admin@maquina.app
|
39
|
+
|
30
40
|
help:
|
31
41
|
maquina/plan:
|
32
42
|
name: Descriptive plan name
|
@@ -34,6 +44,8 @@ en:
|
|
34
44
|
price: Price for not free plans
|
35
45
|
free: Check if plan is free of cost
|
36
46
|
active: Active plans are available for customers
|
47
|
+
first_runs:
|
48
|
+
passwords: Use a strong password. The password must contain at least 8 characters, including uppercase and lowercase letters, numbers and special characters.
|
37
49
|
|
38
50
|
helpers:
|
39
51
|
cancel: Cancel
|
@@ -45,6 +57,9 @@ en:
|
|
45
57
|
maquina/invitation:
|
46
58
|
create: Send invitation
|
47
59
|
|
60
|
+
first_runs:
|
61
|
+
create: Create admin account
|
62
|
+
|
48
63
|
maxlength:
|
49
64
|
default: 30
|
50
65
|
|
@@ -53,6 +68,20 @@ en:
|
|
53
68
|
trial: 3
|
54
69
|
price: 9
|
55
70
|
|
71
|
+
first_runs:
|
72
|
+
email: 60
|
73
|
+
password: 30
|
74
|
+
|
56
75
|
sessions:
|
57
76
|
email: 60
|
58
77
|
password: 60
|
78
|
+
|
79
|
+
placeholder:
|
80
|
+
|
81
|
+
help:
|
82
|
+
maquina/plan:
|
83
|
+
name: Descriptive and unique plan name
|
84
|
+
trial: Number of days on trial
|
85
|
+
price: Price for not free plans
|
86
|
+
free: Check if plan is free of cost
|
87
|
+
active: Active plans are available for customers
|
data/config/locales/forms.es.yml
CHANGED
@@ -1,11 +1,21 @@
|
|
1
1
|
es:
|
2
2
|
new:
|
3
|
+
title: Nuevo %{model}
|
4
|
+
description: Complete el formulario para crear un nuevo %{model}
|
3
5
|
maquina/plan:
|
4
|
-
title:
|
6
|
+
title: Crear plan de suscripción
|
7
|
+
description: Configura un nuevo plan de suscripción con precios y características
|
5
8
|
maquina/invitation:
|
6
9
|
title: Invitar a un usuario a colaborar
|
7
10
|
description: El usuario va a recibir la invitación para unirse a colaborar vía correo electrónico.
|
8
11
|
|
12
|
+
edit:
|
13
|
+
title: Editar %{model}
|
14
|
+
description: Modificar los detalles de %{model}
|
15
|
+
maquina/plan:
|
16
|
+
title: Editar plan de suscripción
|
17
|
+
description: Modifica los detalles y configuraciones del plan de suscripción
|
18
|
+
|
9
19
|
form:
|
10
20
|
sessions:
|
11
21
|
email: Correo electrónico
|
@@ -21,22 +31,13 @@ es:
|
|
21
31
|
email: user@mail.com
|
22
32
|
password: Contraseña actual
|
23
33
|
|
24
|
-
maquina/plan:
|
25
|
-
name: Plan básico
|
26
|
-
|
27
34
|
maquina/invitation:
|
28
35
|
email: Correo electrónico del usuario
|
29
36
|
|
30
37
|
first_runs:
|
31
|
-
email: admin@
|
38
|
+
email: admin@maquina.app
|
32
39
|
|
33
40
|
help:
|
34
|
-
maquina/plan:
|
35
|
-
name: Nombre del plan
|
36
|
-
trial: Número de días de prueba
|
37
|
-
price: Precio del plan
|
38
|
-
free: Indica si el plan es gratuito
|
39
|
-
active: Activar plan para clientes
|
40
41
|
first_runs:
|
41
42
|
password: Utilize una contraseña robusta. La contraseña debe contener al menos 8 caracteres, incluyendo letras mayúsculas y minúsculas, números y caracteres especiales.
|
42
43
|
|
@@ -68,3 +69,13 @@ es:
|
|
68
69
|
sessions:
|
69
70
|
email: 60
|
70
71
|
password: 60
|
72
|
+
|
73
|
+
placeholder:
|
74
|
+
|
75
|
+
help:
|
76
|
+
maquina/plan:
|
77
|
+
name: Nombre del plan
|
78
|
+
trial: Número de días de prueba
|
79
|
+
price: Precio del plan
|
80
|
+
free: Indica si el plan es gratuito
|
81
|
+
active: Activar plan para clientes
|