maquina 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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +8 -0
- data/app/assets/images/maquina/maquina.svg +18 -0
- data/app/assets/javascripts/maquina/application.js +4 -0
- data/app/assets/javascripts/maquina/controllers/alert_controller.js +29 -0
- data/app/assets/javascripts/maquina/controllers/application.js +9 -0
- data/app/assets/javascripts/maquina/controllers/file_controller.js +60 -0
- data/app/assets/javascripts/maquina/controllers/index.js +11 -0
- data/app/assets/javascripts/maquina/controllers/mobile_menu_controller.js +31 -0
- data/app/assets/javascripts/maquina/controllers/modal_controller.js +39 -0
- data/app/assets/javascripts/maquina/controllers/modal_open_controller.js +15 -0
- data/app/assets/javascripts/maquina/controllers/popup_menu_controller.js +17 -0
- data/app/assets/javascripts/maquina/controllers/submit_form_controller.js +11 -0
- data/app/assets/stylesheets/maquina/application.css +15 -0
- data/app/assets/stylesheets/maquina/application.tailwind.css +102 -0
- data/app/controllers/concerns/maquina/authenticate.rb +41 -0
- data/app/controllers/concerns/maquina/create.rb +27 -0
- data/app/controllers/concerns/maquina/destroy.rb +28 -0
- data/app/controllers/concerns/maquina/edit.rb +29 -0
- data/app/controllers/concerns/maquina/index.rb +33 -0
- data/app/controllers/concerns/maquina/new.rb +22 -0
- data/app/controllers/concerns/maquina/resourceful.rb +180 -0
- data/app/controllers/concerns/maquina/show.rb +27 -0
- data/app/controllers/concerns/maquina/update.rb +31 -0
- data/app/controllers/maquina/accept_invitations_controller.rb +28 -0
- data/app/controllers/maquina/application_controller.rb +19 -0
- data/app/controllers/maquina/dashboard_controller.rb +16 -0
- data/app/controllers/maquina/invitations_controller.rb +51 -0
- data/app/controllers/maquina/plans_controller.rb +56 -0
- data/app/controllers/maquina/sessions_controller.rb +56 -0
- data/app/controllers/maquina/unauthorized_controller.rb +9 -0
- data/app/controllers/maquina/users_controller.rb +24 -0
- data/app/helpers/maquina/application_helper.rb +19 -0
- data/app/helpers/maquina/navbar_menu_helper.rb +29 -0
- data/app/helpers/maquina/views_helper.rb +9 -0
- data/app/jobs/maquina/application_job.rb +4 -0
- data/app/mailers/maquina/application_mailer.rb +8 -0
- data/app/mailers/maquina/user_notifications_mailer.rb +16 -0
- data/app/models/concerns/maquina/authenticate_by.rb +33 -0
- data/app/models/concerns/maquina/blockeable.rb +28 -0
- data/app/models/concerns/maquina/multifactor.rb +80 -0
- data/app/models/concerns/maquina/retain_passwords.rb +32 -0
- data/app/models/concerns/maquina/searchable.rb +24 -0
- data/app/models/maquina/active_session.rb +33 -0
- data/app/models/maquina/application_record.rb +11 -0
- data/app/models/maquina/current.rb +21 -0
- data/app/models/maquina/invitation.rb +15 -0
- data/app/models/maquina/plan.rb +38 -0
- data/app/models/maquina/used_password.rb +17 -0
- data/app/models/maquina/user.rb +33 -0
- data/app/policies/maquina/application_policy.rb +50 -0
- data/app/policies/maquina/invitation_policy.rb +13 -0
- data/app/policies/maquina/navigation_policy.rb +13 -0
- data/app/policies/maquina/plan_policy.rb +49 -0
- data/app/policies/maquina/user_policy.rb +27 -0
- data/app/views/layouts/maquina/application.html.erb +26 -0
- data/app/views/layouts/maquina/mailer.html.erb +377 -0
- data/app/views/layouts/maquina/mailer.text.erb +12 -0
- data/app/views/layouts/maquina/sessions.html.erb +24 -0
- data/app/views/maquina/accept_invitations/new.html.erb +9 -0
- data/app/views/maquina/accept_invitations/new_view.rb +41 -0
- data/app/views/maquina/application/_navbar.html.erb +21 -0
- data/app/views/maquina/application/alert.rb +104 -0
- data/app/views/maquina/application/components/action_text_component.rb +20 -0
- data/app/views/maquina/application/components/checkbox_component.rb +21 -0
- data/app/views/maquina/application/components/component_base.rb +60 -0
- data/app/views/maquina/application/components/file_component.rb +59 -0
- data/app/views/maquina/application/components/input_component.rb +20 -0
- data/app/views/maquina/application/components/select_component.rb +44 -0
- data/app/views/maquina/application/create.turbo_stream.erb +11 -0
- data/app/views/maquina/application/edit.html.erb +9 -0
- data/app/views/maquina/application/edit.rb +17 -0
- data/app/views/maquina/application/form.rb +77 -0
- data/app/views/maquina/application/index.html.erb +10 -0
- data/app/views/maquina/application/index_header.rb +46 -0
- data/app/views/maquina/application/index_modal.rb +43 -0
- data/app/views/maquina/application/index_table.rb +121 -0
- data/app/views/maquina/application/new.html.erb +9 -0
- data/app/views/maquina/application/new.rb +18 -0
- data/app/views/maquina/application/sessions_header.rb +31 -0
- data/app/views/maquina/application/show.html.erb +1 -0
- data/app/views/maquina/application/update.turbo_stream.erb +11 -0
- data/app/views/maquina/application_view.rb +46 -0
- data/app/views/maquina/dashboard/index.html.erb +0 -0
- data/app/views/maquina/invitations/create.turbo_stream.erb +13 -0
- data/app/views/maquina/invitations/new.html.erb +3 -0
- data/app/views/maquina/navbar/menu.rb +62 -0
- data/app/views/maquina/navbar/menu_item_link.rb +34 -0
- data/app/views/maquina/navbar/mobile_button.rb +29 -0
- data/app/views/maquina/navbar/mobile_menu.rb +47 -0
- data/app/views/maquina/navbar/notification.rb +37 -0
- data/app/views/maquina/navbar/profile.rb +16 -0
- data/app/views/maquina/navbar/profile_button.rb +24 -0
- data/app/views/maquina/navbar/profile_menu.rb +108 -0
- data/app/views/maquina/navbar/profile_menu_item_link.rb +41 -0
- data/app/views/maquina/navbar/search.rb +40 -0
- data/app/views/maquina/navbar/title.rb +22 -0
- data/app/views/maquina/sessions/create.turbo_stream.erb +11 -0
- data/app/views/maquina/sessions/form.rb +56 -0
- data/app/views/maquina/sessions/new.html.erb +9 -0
- data/app/views/maquina/unauthorized/401.html.erb +1 -0
- data/app/views/maquina/user_notifications_mailer/invitation_email.html.erb +40 -0
- data/app/views/maquina/user_notifications_mailer/invitation_email.text.erb +12 -0
- data/config/definitions.rb +1 -0
- data/config/initializers/importmap.rb +17 -0
- data/config/initializers/money.rb +116 -0
- data/config/initializers/pagy.rb +235 -0
- data/config/locales/flash.en.yml +44 -0
- data/config/locales/forms.en.yml +58 -0
- data/config/locales/mailers.en.yml +35 -0
- data/config/locales/models.en.yml +34 -0
- data/config/locales/routes.en.yml +7 -0
- data/config/locales/views.en.yml +45 -0
- data/config/routes.rb +17 -0
- data/db/migrate/20221109010726_create_maquina_plans.rb +13 -0
- data/db/migrate/20221113000409_create_maquina_users.rb +19 -0
- data/db/migrate/20221113020108_create_maquina_used_passwords.rb +10 -0
- data/db/migrate/20221115223414_create_maquina_active_sessions.rb +15 -0
- data/db/migrate/20230201203922_create_maquina_invitations.rb +12 -0
- data/db/schema.rb +1 -0
- data/lib/generators/maquina/install_generator.rb +32 -0
- data/lib/generators/maquina/install_templates/install_templates_generator.rb +31 -0
- data/lib/generators/maquina/tailwind_config/tailwind_config_generator.rb +11 -0
- data/lib/generators/maquina/tailwind_config/templates/app/assets/config/maquina/tailwind.config.js.tt +68 -0
- data/lib/generators/maquina/templates/config/initializers/maquina.rb +3 -0
- data/lib/maquina/engine.rb +17 -0
- data/lib/maquina/version.rb +3 -0
- data/lib/maquina.rb +48 -0
- data/lib/tasks/install.rake +19 -0
- data/lib/tasks/maquina_tasks.rake +4 -0
- data/lib/tasks/tailwind.rake +25 -0
- metadata +456 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Maquina
|
|
4
|
+
module Navbar
|
|
5
|
+
class MenuItemLink < Phlex::HTML
|
|
6
|
+
include ApplicationView
|
|
7
|
+
delegate :t, :active_menu_option?, to: :helpers
|
|
8
|
+
|
|
9
|
+
def initialize(option, path, desktop: true, active: false)
|
|
10
|
+
@option = option
|
|
11
|
+
@path = path
|
|
12
|
+
@active = active
|
|
13
|
+
@desktop = desktop
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def template
|
|
17
|
+
a(href: @path, **classes("#{link_type}-menu-item", active?: "#{link_type}-menu-item__active")) { t("menu.main.#{@option}") }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def active?
|
|
23
|
+
active_menu_option?(@path)
|
|
24
|
+
rescue NoMethodError => ex
|
|
25
|
+
Rails.logger.error "[#{self.class}] Please implement helper method :active_menu_option?(path) :: #{ex.message}"
|
|
26
|
+
false
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def link_type
|
|
30
|
+
@desktop ? "desktop" : "mobile"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Maquina
|
|
4
|
+
module Navbar
|
|
5
|
+
class MobileButton < Phlex::HTML
|
|
6
|
+
include ApplicationView
|
|
7
|
+
|
|
8
|
+
def template(&block)
|
|
9
|
+
div class: "flex items-center lg:hidden" do
|
|
10
|
+
button type: "button", class: "mobile-button", "aria-controls": "mobile-menu", "aria-expanded": false, "data-action": "mobile-menu#toggle" do
|
|
11
|
+
span(class: "sr-only") { "Open main menu" }
|
|
12
|
+
svg_icon(:outline, data: {"mobile-menu-target": :open}, icon: closed_icon)
|
|
13
|
+
svg_icon(:outline, css_class: "hidden h-6 -w6", data: {"mobile-menu-target": :close}, icon: opened_icon)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def closed_icon
|
|
21
|
+
"M4 6h16M4 12h16M4 18h16"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def opened_icon
|
|
25
|
+
"M6 18L18 6M6 6l12 12"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Maquina
|
|
4
|
+
module Navbar
|
|
5
|
+
class MobileMenu < Phlex::HTML
|
|
6
|
+
include ApplicationView
|
|
7
|
+
|
|
8
|
+
def template
|
|
9
|
+
div class: "hidden lg:hidden", **data_attributes do
|
|
10
|
+
render Maquina::Navbar::Menu.new(desktop: false)
|
|
11
|
+
|
|
12
|
+
div class: "pt-4 pb-3 border-t border-gray-200" do
|
|
13
|
+
div class: "flex items-center px-4" do
|
|
14
|
+
div class: "flex-shrink-0" do
|
|
15
|
+
img class: "h-10 w-10 rounded-full", src: "https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80", alt: ""
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
div class: "ml-3" do
|
|
19
|
+
div(class: "text-base font-medium text-skin-muted") { "Tom Cook" }
|
|
20
|
+
div(class: "text-sm font-medium text-skin-dimmed") { "tom@example.com" }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
render Maquina::Navbar::Notification.new(desktop: false)
|
|
24
|
+
end
|
|
25
|
+
render Maquina::Navbar::ProfileMenu.new(desktop: false)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def data_attributes
|
|
33
|
+
{
|
|
34
|
+
data: {
|
|
35
|
+
"mobile-menu-target": "menu",
|
|
36
|
+
"transition-enter": "transition ease-out duration-100",
|
|
37
|
+
"transition-enter-active": "transform opacity-0 scale-95",
|
|
38
|
+
"transition-enter-to": "transform opacity-100 scale-100",
|
|
39
|
+
"transition-leave": "transition ease-in duration-75",
|
|
40
|
+
"transition-leave-active": "transform opacity-100 scale-100",
|
|
41
|
+
"transition-leave-to": "transform opacity-0 scale-95"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Maquina
|
|
4
|
+
module Navbar
|
|
5
|
+
class Notification < Phlex::HTML
|
|
6
|
+
include ApplicationView
|
|
7
|
+
|
|
8
|
+
def initialize(desktop: true)
|
|
9
|
+
@desktop = desktop
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def template(&block)
|
|
13
|
+
button(**button_attributes) do
|
|
14
|
+
span(class: "sr-only") { "View notifications" }
|
|
15
|
+
svg_icon(:fill, icon: notification_icon)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def notification_icon
|
|
22
|
+
"M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def button_attributes
|
|
26
|
+
Rails.logger.warn "[#{self.class}] Notification is hidden"
|
|
27
|
+
attrs = {type: "button"}
|
|
28
|
+
|
|
29
|
+
# Hidden by default
|
|
30
|
+
# attrs[:class] = @desktop ? "hidden lg:block notification-icon" : "ml-auto notification-icon"
|
|
31
|
+
attrs[:class] = @desktop ? "hidden notification-icon" : "hidden ml-auto notification-icon"
|
|
32
|
+
|
|
33
|
+
attrs
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Maquina
|
|
4
|
+
module Navbar
|
|
5
|
+
class Profile < Phlex::HTML
|
|
6
|
+
include ApplicationView
|
|
7
|
+
|
|
8
|
+
def template
|
|
9
|
+
div class: "ml-4 relative flex-shrink-0", "data-controller": "popup-menu" do
|
|
10
|
+
render Maquina::Navbar::ProfileButton.new
|
|
11
|
+
render Maquina::Navbar::ProfileMenu.new
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Maquina
|
|
4
|
+
module Navbar
|
|
5
|
+
class ProfileButton < Phlex::HTML
|
|
6
|
+
include ApplicationView
|
|
7
|
+
|
|
8
|
+
def template
|
|
9
|
+
div do
|
|
10
|
+
button type: "button", class: "bg-white rounded-full hidden lg:flex text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", id: "user-menu-button", "aria-expanded": false, "aria-haspopup": true, "data-action": "popup-menu#toggleTransition" do
|
|
11
|
+
span(class: "sr-only") { "Open user menu" }
|
|
12
|
+
svg_icon(:outline, stroke_width: 1.5, icon: user_icon)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def user_icon
|
|
20
|
+
"M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Maquina
|
|
4
|
+
module Navbar
|
|
5
|
+
# == Desktop profile menu at Navbar
|
|
6
|
+
#
|
|
7
|
+
# Renders dropdown profile menu on desktop.
|
|
8
|
+
#
|
|
9
|
+
# Requires Rails Helper method to get access to options for this menu. Method `profile_menu_options` example:
|
|
10
|
+
#
|
|
11
|
+
# def profile_menu_options
|
|
12
|
+
# if Current.user.present?
|
|
13
|
+
# {
|
|
14
|
+
# profile: profiles_path
|
|
15
|
+
# signout: { method: :delete, path: session_path }
|
|
16
|
+
# }
|
|
17
|
+
# else
|
|
18
|
+
# {
|
|
19
|
+
# signin: new_session_path
|
|
20
|
+
# }
|
|
21
|
+
# end
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# `ProfileMenu` uses I18n to translate menu options. Translation example:
|
|
25
|
+
#
|
|
26
|
+
# menu:
|
|
27
|
+
# profile:
|
|
28
|
+
# profile: My profile
|
|
29
|
+
# signout: Close session
|
|
30
|
+
# signin: Login
|
|
31
|
+
#
|
|
32
|
+
# `ProfileMenu` is initialized by default to render links for desktop. To render mobile links,
|
|
33
|
+
# initialize `ProfileMenu` as follow:
|
|
34
|
+
#
|
|
35
|
+
# Views::Navbar::ProfileMenu.new(desktop: false)
|
|
36
|
+
#
|
|
37
|
+
class ProfileMenu < Phlex::HTML
|
|
38
|
+
include ApplicationView
|
|
39
|
+
delegate :t, :profile_menu_options, to: :helpers
|
|
40
|
+
|
|
41
|
+
def initialize(desktop: true)
|
|
42
|
+
@desktop = desktop
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def template
|
|
46
|
+
div(**menu_attributes) do
|
|
47
|
+
secure_menu_options.each_pair do |option, path|
|
|
48
|
+
link_path, attributes = path_options(path)
|
|
49
|
+
render Maquina::Navbar::ProfileMenuItemLink.new(option, link_path, attributes, desktop: @desktop)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def path_options(options)
|
|
57
|
+
attrs = {}
|
|
58
|
+
path = ""
|
|
59
|
+
|
|
60
|
+
if options.is_a?(Hash)
|
|
61
|
+
path = options[:path]
|
|
62
|
+
attrs[:method] = options[:method]
|
|
63
|
+
else
|
|
64
|
+
path = options
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
[path, attrs]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def secure_menu_options
|
|
71
|
+
profile_menu_options
|
|
72
|
+
rescue NoMethodError => ex
|
|
73
|
+
Rails.logger.error "[#{self.class}] Please implement helper method :profile_menu_options :: #{ex.message}"
|
|
74
|
+
{}
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def menu_attributes
|
|
78
|
+
attrs = {class: "mt-3 space-y-1"}
|
|
79
|
+
|
|
80
|
+
if @desktop
|
|
81
|
+
attrs = {
|
|
82
|
+
class: "hidden origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5 focus:outline-none",
|
|
83
|
+
role: "menu",
|
|
84
|
+
"aria-orientation": "vertical",
|
|
85
|
+
"aria-labelledby": "user-menu-button",
|
|
86
|
+
tabindex: "-1"
|
|
87
|
+
}.merge(**data_attributes)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
attrs
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def data_attributes
|
|
94
|
+
{
|
|
95
|
+
data: {
|
|
96
|
+
"popup-menu-target": "menu",
|
|
97
|
+
"transition-enter": "transition ease-out duration-100",
|
|
98
|
+
"transition-enter-active": "transform opacity-0 scale-95",
|
|
99
|
+
"transition-enter-to": "transform opacity-100 scale-100",
|
|
100
|
+
"transition-leave": "transition ease-in duration-75",
|
|
101
|
+
"transition-leave-active": "transform opacity-100 scale-100",
|
|
102
|
+
"transition-leave-to": "transform opacity-0 scale-95"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Maquina
|
|
4
|
+
module Navbar
|
|
5
|
+
class ProfileMenuItemLink < Phlex::HTML
|
|
6
|
+
include ApplicationView
|
|
7
|
+
delegate :t, to: :helpers
|
|
8
|
+
|
|
9
|
+
def initialize(option, path, attributes, desktop: true, active: false)
|
|
10
|
+
@option = option
|
|
11
|
+
@path = path
|
|
12
|
+
@attributes = attributes
|
|
13
|
+
@desktop = desktop
|
|
14
|
+
@active = active
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def template
|
|
18
|
+
link_method = link_attributes.dig(:method)
|
|
19
|
+
if link_method == :delete
|
|
20
|
+
button_to t("menu.profile.#{@option}"), @path, method: :delete, **link_attributes
|
|
21
|
+
else
|
|
22
|
+
a(href: @path, **link_attributes) { t("menu.profile.#{@option}") }
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def link_attributes
|
|
29
|
+
attrs = {
|
|
30
|
+
role: "menuitem",
|
|
31
|
+
tabindex: "-1"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
attrs[:class] = @desktop ? "desktop-profile-menu-item" : "mobile-profile-menu-item"
|
|
35
|
+
|
|
36
|
+
attrs.merge!(@attributes) if @attributes.is_a?(Hash) && @attributes.any?
|
|
37
|
+
attrs
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Maquina
|
|
4
|
+
module Navbar
|
|
5
|
+
class Search < Phlex::HTML
|
|
6
|
+
include ApplicationView
|
|
7
|
+
include Phlex::Rails::Helpers::FormWith
|
|
8
|
+
|
|
9
|
+
delegate :t, to: :helpers
|
|
10
|
+
|
|
11
|
+
def initialize(url: nil, query: "")
|
|
12
|
+
@url = url
|
|
13
|
+
@query = query
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def template
|
|
17
|
+
div class: "flex-1 flex items-center justify-center px-2 lg:ml-6 lg:justify-end" do
|
|
18
|
+
div class: "max-w-lg w-full lg:max-w-xs" do
|
|
19
|
+
form_with(url: @url, method: :get, data: {controller: "submit-form"}) do |form|
|
|
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
|
+
svg_icon(:fill, view_box: "0 0 20 20", css_class: "h-5 w-5 text-skin-dimmed", icon: 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
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def search_icon
|
|
36
|
+
"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"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Maquina
|
|
4
|
+
module Navbar
|
|
5
|
+
class Title < Phlex::HTML
|
|
6
|
+
include Maquina::ApplicationView
|
|
7
|
+
|
|
8
|
+
def initialize(brand_icon: "")
|
|
9
|
+
@brand_icon = brand_icon
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def template
|
|
13
|
+
div class: "flex-shrink-0 flex items-center" do
|
|
14
|
+
if @brand_icon.present?
|
|
15
|
+
img(src: @brand_icon, class: "hidden lg:block mx-auto h-8 w-auto")
|
|
16
|
+
img(src: @brand_icon, class: "block lg:hidden mx-auto h-8 w-auto")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<%= turbo_stream.replace :form do %>
|
|
2
|
+
<%= turbo_frame_tag :form do %>
|
|
3
|
+
<%= render Maquina::Sessions::Form.new(OpenStruct.new(params.slice(:email, :password, :return_to))) %>
|
|
4
|
+
<% end %>
|
|
5
|
+
<% end %>
|
|
6
|
+
|
|
7
|
+
<%= turbo_stream.replace :alert do %>
|
|
8
|
+
<%= turbo_frame_tag :alert do %>
|
|
9
|
+
<%= render Maquina::Application::Alert.new(flash) %>
|
|
10
|
+
<% end %>
|
|
11
|
+
<% end %>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Maquina
|
|
4
|
+
module Sessions
|
|
5
|
+
class Form < Phlex::HTML
|
|
6
|
+
include Maquina::ApplicationView
|
|
7
|
+
include Phlex::Rails::Helpers::FormWith
|
|
8
|
+
|
|
9
|
+
def initialize(resource)
|
|
10
|
+
@resource = resource
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def template
|
|
14
|
+
div(class: "mt-8 sm:mx-auto sm:w-full sm:max-w-md") do
|
|
15
|
+
div(class: "bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10") do
|
|
16
|
+
form_with(url: sessions_path, method: :post, class: "space-y-6", data: {turbo_frame: :_top, "turbo-cache": false}) do |form|
|
|
17
|
+
form.hidden_field :return_to, value: @resource.return_to
|
|
18
|
+
div do
|
|
19
|
+
form.label t("form.sessions.email"), for: :email, class: "block label"
|
|
20
|
+
div(class: "mt-1") do
|
|
21
|
+
form.text_field :email, value: @resource.email, type: :email, required: true, class: "w-full block input", **field_attributes(:email)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
div do
|
|
25
|
+
form.label t("form.sessions.password"), for: :password, class: "block label"
|
|
26
|
+
div(class: "mt-1") do
|
|
27
|
+
form.password_field :password, value: @resource.password, required: true, class: "w-full block input", **field_attributes(:password)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
div(class: "flex items-center justify-between") do
|
|
31
|
+
div do
|
|
32
|
+
end
|
|
33
|
+
div(class: "text-sm") do
|
|
34
|
+
a(href: "#", class: "link", data: {turbo_frame: :_top}) { t("form.sessions.forgot_password") }
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
div do
|
|
38
|
+
form.submit t("form.sessions.submit"), class: "flex w-full justify-center button button-accented"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def field_attributes(field_name, scope = nil)
|
|
48
|
+
scope ||= "sessions"
|
|
49
|
+
{
|
|
50
|
+
maxlength: t("maxlength.#{scope}.#{field_name}", default: t("maxlength.default")),
|
|
51
|
+
placeholder: t("placeholder.#{scope}.#{field_name}", default: "")
|
|
52
|
+
}
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<div class="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
|
2
|
+
|
|
3
|
+
<%= render Maquina::Application::SessionsHeader.new(brand_icon: brand_icon) %>
|
|
4
|
+
|
|
5
|
+
<%= turbo_frame_tag :form do %>
|
|
6
|
+
<%= render Maquina::Sessions::Form.new(OpenStruct.new(params.slice(:email, :password, :return_to))) %>
|
|
7
|
+
<% end %>
|
|
8
|
+
|
|
9
|
+
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
401
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<h1 class="mt-0 text-2xl font-bold text-left text-gray-postmark-darker">
|
|
2
|
+
<%= t("mailers.invitation_email.hello") %>
|
|
3
|
+
</h1>
|
|
4
|
+
<p class="mt-1_5 mb-5px text-base leading-6 text-gray-postmark-dark">
|
|
5
|
+
<%= t("mailers.invitation_email.content_1", inviteer: @inviteer, org: @org, product: t("mailers.product_name")) %>
|
|
6
|
+
</p>
|
|
7
|
+
<table align="center" class="w-full text-center my-7_5 mx-auto" cellpadding="0" cellspacing="0" role="presentation">
|
|
8
|
+
<tr>
|
|
9
|
+
<td align="center">
|
|
10
|
+
<table class="w-full" cellpadding="0" cellspacing="0" role="presentation">
|
|
11
|
+
<tr>
|
|
12
|
+
<td align="center" class="text-base">
|
|
13
|
+
<a href="<%= @url %>" class="button button--blue"><%= t("mailers.invitation_email.action") %></a>
|
|
14
|
+
</td>
|
|
15
|
+
</tr>
|
|
16
|
+
</table>
|
|
17
|
+
</td>
|
|
18
|
+
</tr>
|
|
19
|
+
</table>
|
|
20
|
+
<p class="mt-1_5 mb-5px text-base leading-6 text-gray-postmark-dark">
|
|
21
|
+
<%= t("mailers.invitation_email.content_2").html_safe %>
|
|
22
|
+
</p>
|
|
23
|
+
<p class="mt-2_5 mb-5px text-base leading-6 text-gray-postmark-dark">
|
|
24
|
+
<%= t("mailers.invitation_email.content_3", org: @org).html_safe %>
|
|
25
|
+
</p>
|
|
26
|
+
<p class="mt-2_5 mb-5px text-base leading-6 text-gray-postmark-dark">
|
|
27
|
+
<%= t("mailers.invitation_email.content_4").html_safe %>
|
|
28
|
+
</p>
|
|
29
|
+
<table class="body-sub" cellpadding="0" cellspacing="0" role="presentation">
|
|
30
|
+
<tr>
|
|
31
|
+
<td>
|
|
32
|
+
<p class="mt-1_5 mb-5px text-xs leading-6 text-gray-postmark-dark">
|
|
33
|
+
<%= t("mailers.invitation_email.footer") %>
|
|
34
|
+
</p>
|
|
35
|
+
<p class="mt-1_5 mb-5px text-xs leading-6 text-gray-postmark-dark">
|
|
36
|
+
<%= @url %>
|
|
37
|
+
</p>
|
|
38
|
+
</td>
|
|
39
|
+
</tr>
|
|
40
|
+
</table>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<%= t("mailers.invitation_email.hello") %>
|
|
2
|
+
|
|
3
|
+
<%= t("mailers.invitation_email.content_1_text", inviteer: @inviteer, org: @org, product: t("mailers.product_name")) %>
|
|
4
|
+
|
|
5
|
+
<%= @url %>
|
|
6
|
+
|
|
7
|
+
<%= t("mailers.invitation_email.content_2_text") %>
|
|
8
|
+
|
|
9
|
+
<%= t("mailers.invitation_email.content_3_1_text") %>
|
|
10
|
+
<%= t("mailers.invitation_email.content_3_2_text", org: @org) %>
|
|
11
|
+
|
|
12
|
+
<%= t("mailers.invitation_email.content_4_text") %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/Users/marioch/Development/butaca2/config/definitions.rb
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "maquina"
|
|
4
|
+
|
|
5
|
+
Maquina.configuration.importmap.draw do
|
|
6
|
+
# Stimulus & Turbo
|
|
7
|
+
pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
|
|
8
|
+
pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
|
|
9
|
+
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
|
|
10
|
+
pin "stimulus-use", to: "https://ga.jspm.io/npm:stimulus-use@0.51.0/dist/index.js"
|
|
11
|
+
pin "hotkeys-js", to: "https://ga.jspm.io/npm:hotkeys-js@3.10.0/dist/hotkeys.esm.js"
|
|
12
|
+
|
|
13
|
+
# Maquina entrypoint
|
|
14
|
+
pin "application", to: "maquina/application.js", preload: true
|
|
15
|
+
|
|
16
|
+
pin_all_from Maquina::Engine.root.join("app/assets/javascripts/maquina/controllers"), under: "controllers", to: "maquina/controllers"
|
|
17
|
+
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "money-rails"
|
|
4
|
+
|
|
5
|
+
MoneyRails.configure do |config|
|
|
6
|
+
# To set the default currency
|
|
7
|
+
#
|
|
8
|
+
# config.default_currency = :usd
|
|
9
|
+
|
|
10
|
+
# Set default bank object
|
|
11
|
+
#
|
|
12
|
+
# Example:
|
|
13
|
+
# config.default_bank = EuCentralBank.new
|
|
14
|
+
|
|
15
|
+
# Add exchange rates to current money bank object.
|
|
16
|
+
# (The conversion rate refers to one direction only)
|
|
17
|
+
#
|
|
18
|
+
# Example:
|
|
19
|
+
# config.add_rate "USD", "CAD", 1.24515
|
|
20
|
+
# config.add_rate "CAD", "USD", 0.803115
|
|
21
|
+
|
|
22
|
+
# To handle the inclusion of validations for monetized fields
|
|
23
|
+
# The default value is true
|
|
24
|
+
#
|
|
25
|
+
# config.include_validations = true
|
|
26
|
+
|
|
27
|
+
# Default ActiveRecord migration configuration values for columns:
|
|
28
|
+
#
|
|
29
|
+
# config.amount_column = { prefix: '', # column name prefix
|
|
30
|
+
# postfix: '_cents', # column name postfix
|
|
31
|
+
# column_name: nil, # full column name (overrides prefix, postfix and accessor name)
|
|
32
|
+
# type: :integer, # column type
|
|
33
|
+
# present: true, # column will be created
|
|
34
|
+
# null: false, # other options will be treated as column options
|
|
35
|
+
# default: 0
|
|
36
|
+
# }
|
|
37
|
+
#
|
|
38
|
+
# config.currency_column = { prefix: '',
|
|
39
|
+
# postfix: '_currency',
|
|
40
|
+
# column_name: nil,
|
|
41
|
+
# type: :string,
|
|
42
|
+
# present: true,
|
|
43
|
+
# null: false,
|
|
44
|
+
# default: 'USD'
|
|
45
|
+
# }
|
|
46
|
+
|
|
47
|
+
# Register a custom currency
|
|
48
|
+
#
|
|
49
|
+
# Example:
|
|
50
|
+
# config.register_currency = {
|
|
51
|
+
# priority: 1,
|
|
52
|
+
# iso_code: "EU4",
|
|
53
|
+
# name: "Euro with subunit of 4 digits",
|
|
54
|
+
# symbol: "€",
|
|
55
|
+
# symbol_first: true,
|
|
56
|
+
# subunit: "Subcent",
|
|
57
|
+
# subunit_to_unit: 10000,
|
|
58
|
+
# thousands_separator: ".",
|
|
59
|
+
# decimal_mark: ","
|
|
60
|
+
# }
|
|
61
|
+
|
|
62
|
+
# Specify a rounding mode
|
|
63
|
+
# Any one of:
|
|
64
|
+
#
|
|
65
|
+
# BigDecimal::ROUND_UP,
|
|
66
|
+
# BigDecimal::ROUND_DOWN,
|
|
67
|
+
# BigDecimal::ROUND_HALF_UP,
|
|
68
|
+
# BigDecimal::ROUND_HALF_DOWN,
|
|
69
|
+
# BigDecimal::ROUND_HALF_EVEN,
|
|
70
|
+
# BigDecimal::ROUND_CEILING,
|
|
71
|
+
# BigDecimal::ROUND_FLOOR
|
|
72
|
+
#
|
|
73
|
+
# set to BigDecimal::ROUND_HALF_EVEN by default
|
|
74
|
+
#
|
|
75
|
+
config.rounding_mode = BigDecimal::ROUND_HALF_UP
|
|
76
|
+
|
|
77
|
+
# Set default money format globally.
|
|
78
|
+
# Default value is nil meaning "ignore this option".
|
|
79
|
+
# Example:
|
|
80
|
+
#
|
|
81
|
+
# config.default_format = {
|
|
82
|
+
# no_cents_if_whole: nil,
|
|
83
|
+
# symbol: nil,
|
|
84
|
+
# sign_before_symbol: nil
|
|
85
|
+
# }
|
|
86
|
+
|
|
87
|
+
# If you would like to use I18n localization (formatting depends on the
|
|
88
|
+
# locale):
|
|
89
|
+
config.locale_backend = :i18n
|
|
90
|
+
#
|
|
91
|
+
# Example (using default localization from rails-i18n):
|
|
92
|
+
#
|
|
93
|
+
# I18n.locale = :en
|
|
94
|
+
# Money.new(10_000_00, 'USD').format # => $10,000.00
|
|
95
|
+
# I18n.locale = :es
|
|
96
|
+
# Money.new(10_000_00, 'USD').format # => $10.000,00
|
|
97
|
+
#
|
|
98
|
+
# For the legacy behaviour of "per currency" localization (formatting depends
|
|
99
|
+
# only on currency):
|
|
100
|
+
# config.locale_backend = :currency
|
|
101
|
+
#
|
|
102
|
+
# Example:
|
|
103
|
+
# Money.new(10_000_00, 'USD').format # => $10,000.00
|
|
104
|
+
# Money.new(10_000_00, 'EUR').format # => €10.000,00
|
|
105
|
+
#
|
|
106
|
+
# In case you don't need localization and would like to use default values
|
|
107
|
+
# (can be redefined using config.default_format):
|
|
108
|
+
# config.locale_backend = nil
|
|
109
|
+
|
|
110
|
+
# Set default raise_error_on_money_parsing option
|
|
111
|
+
# It will be raise error if assigned different currency
|
|
112
|
+
# The default value is false
|
|
113
|
+
#
|
|
114
|
+
# Example:
|
|
115
|
+
# config.raise_error_on_money_parsing = false
|
|
116
|
+
end
|