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