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.
Files changed (134) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +28 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/images/maquina/maquina.svg +18 -0
  6. data/app/assets/javascripts/maquina/application.js +4 -0
  7. data/app/assets/javascripts/maquina/controllers/alert_controller.js +29 -0
  8. data/app/assets/javascripts/maquina/controllers/application.js +9 -0
  9. data/app/assets/javascripts/maquina/controllers/file_controller.js +60 -0
  10. data/app/assets/javascripts/maquina/controllers/index.js +11 -0
  11. data/app/assets/javascripts/maquina/controllers/mobile_menu_controller.js +31 -0
  12. data/app/assets/javascripts/maquina/controllers/modal_controller.js +39 -0
  13. data/app/assets/javascripts/maquina/controllers/modal_open_controller.js +15 -0
  14. data/app/assets/javascripts/maquina/controllers/popup_menu_controller.js +17 -0
  15. data/app/assets/javascripts/maquina/controllers/submit_form_controller.js +11 -0
  16. data/app/assets/stylesheets/maquina/application.css +15 -0
  17. data/app/assets/stylesheets/maquina/application.tailwind.css +102 -0
  18. data/app/controllers/concerns/maquina/authenticate.rb +41 -0
  19. data/app/controllers/concerns/maquina/create.rb +27 -0
  20. data/app/controllers/concerns/maquina/destroy.rb +28 -0
  21. data/app/controllers/concerns/maquina/edit.rb +29 -0
  22. data/app/controllers/concerns/maquina/index.rb +33 -0
  23. data/app/controllers/concerns/maquina/new.rb +22 -0
  24. data/app/controllers/concerns/maquina/resourceful.rb +180 -0
  25. data/app/controllers/concerns/maquina/show.rb +27 -0
  26. data/app/controllers/concerns/maquina/update.rb +31 -0
  27. data/app/controllers/maquina/accept_invitations_controller.rb +28 -0
  28. data/app/controllers/maquina/application_controller.rb +19 -0
  29. data/app/controllers/maquina/dashboard_controller.rb +16 -0
  30. data/app/controllers/maquina/invitations_controller.rb +51 -0
  31. data/app/controllers/maquina/plans_controller.rb +56 -0
  32. data/app/controllers/maquina/sessions_controller.rb +56 -0
  33. data/app/controllers/maquina/unauthorized_controller.rb +9 -0
  34. data/app/controllers/maquina/users_controller.rb +24 -0
  35. data/app/helpers/maquina/application_helper.rb +19 -0
  36. data/app/helpers/maquina/navbar_menu_helper.rb +29 -0
  37. data/app/helpers/maquina/views_helper.rb +9 -0
  38. data/app/jobs/maquina/application_job.rb +4 -0
  39. data/app/mailers/maquina/application_mailer.rb +8 -0
  40. data/app/mailers/maquina/user_notifications_mailer.rb +16 -0
  41. data/app/models/concerns/maquina/authenticate_by.rb +33 -0
  42. data/app/models/concerns/maquina/blockeable.rb +28 -0
  43. data/app/models/concerns/maquina/multifactor.rb +80 -0
  44. data/app/models/concerns/maquina/retain_passwords.rb +32 -0
  45. data/app/models/concerns/maquina/searchable.rb +24 -0
  46. data/app/models/maquina/active_session.rb +33 -0
  47. data/app/models/maquina/application_record.rb +11 -0
  48. data/app/models/maquina/current.rb +21 -0
  49. data/app/models/maquina/invitation.rb +15 -0
  50. data/app/models/maquina/plan.rb +38 -0
  51. data/app/models/maquina/used_password.rb +17 -0
  52. data/app/models/maquina/user.rb +33 -0
  53. data/app/policies/maquina/application_policy.rb +50 -0
  54. data/app/policies/maquina/invitation_policy.rb +13 -0
  55. data/app/policies/maquina/navigation_policy.rb +13 -0
  56. data/app/policies/maquina/plan_policy.rb +49 -0
  57. data/app/policies/maquina/user_policy.rb +27 -0
  58. data/app/views/layouts/maquina/application.html.erb +26 -0
  59. data/app/views/layouts/maquina/mailer.html.erb +377 -0
  60. data/app/views/layouts/maquina/mailer.text.erb +12 -0
  61. data/app/views/layouts/maquina/sessions.html.erb +24 -0
  62. data/app/views/maquina/accept_invitations/new.html.erb +9 -0
  63. data/app/views/maquina/accept_invitations/new_view.rb +41 -0
  64. data/app/views/maquina/application/_navbar.html.erb +21 -0
  65. data/app/views/maquina/application/alert.rb +104 -0
  66. data/app/views/maquina/application/components/action_text_component.rb +20 -0
  67. data/app/views/maquina/application/components/checkbox_component.rb +21 -0
  68. data/app/views/maquina/application/components/component_base.rb +60 -0
  69. data/app/views/maquina/application/components/file_component.rb +59 -0
  70. data/app/views/maquina/application/components/input_component.rb +20 -0
  71. data/app/views/maquina/application/components/select_component.rb +44 -0
  72. data/app/views/maquina/application/create.turbo_stream.erb +11 -0
  73. data/app/views/maquina/application/edit.html.erb +9 -0
  74. data/app/views/maquina/application/edit.rb +17 -0
  75. data/app/views/maquina/application/form.rb +77 -0
  76. data/app/views/maquina/application/index.html.erb +10 -0
  77. data/app/views/maquina/application/index_header.rb +46 -0
  78. data/app/views/maquina/application/index_modal.rb +43 -0
  79. data/app/views/maquina/application/index_table.rb +121 -0
  80. data/app/views/maquina/application/new.html.erb +9 -0
  81. data/app/views/maquina/application/new.rb +18 -0
  82. data/app/views/maquina/application/sessions_header.rb +31 -0
  83. data/app/views/maquina/application/show.html.erb +1 -0
  84. data/app/views/maquina/application/update.turbo_stream.erb +11 -0
  85. data/app/views/maquina/application_view.rb +46 -0
  86. data/app/views/maquina/dashboard/index.html.erb +0 -0
  87. data/app/views/maquina/invitations/create.turbo_stream.erb +13 -0
  88. data/app/views/maquina/invitations/new.html.erb +3 -0
  89. data/app/views/maquina/navbar/menu.rb +62 -0
  90. data/app/views/maquina/navbar/menu_item_link.rb +34 -0
  91. data/app/views/maquina/navbar/mobile_button.rb +29 -0
  92. data/app/views/maquina/navbar/mobile_menu.rb +47 -0
  93. data/app/views/maquina/navbar/notification.rb +37 -0
  94. data/app/views/maquina/navbar/profile.rb +16 -0
  95. data/app/views/maquina/navbar/profile_button.rb +24 -0
  96. data/app/views/maquina/navbar/profile_menu.rb +108 -0
  97. data/app/views/maquina/navbar/profile_menu_item_link.rb +41 -0
  98. data/app/views/maquina/navbar/search.rb +40 -0
  99. data/app/views/maquina/navbar/title.rb +22 -0
  100. data/app/views/maquina/sessions/create.turbo_stream.erb +11 -0
  101. data/app/views/maquina/sessions/form.rb +56 -0
  102. data/app/views/maquina/sessions/new.html.erb +9 -0
  103. data/app/views/maquina/unauthorized/401.html.erb +1 -0
  104. data/app/views/maquina/user_notifications_mailer/invitation_email.html.erb +40 -0
  105. data/app/views/maquina/user_notifications_mailer/invitation_email.text.erb +12 -0
  106. data/config/definitions.rb +1 -0
  107. data/config/initializers/importmap.rb +17 -0
  108. data/config/initializers/money.rb +116 -0
  109. data/config/initializers/pagy.rb +235 -0
  110. data/config/locales/flash.en.yml +44 -0
  111. data/config/locales/forms.en.yml +58 -0
  112. data/config/locales/mailers.en.yml +35 -0
  113. data/config/locales/models.en.yml +34 -0
  114. data/config/locales/routes.en.yml +7 -0
  115. data/config/locales/views.en.yml +45 -0
  116. data/config/routes.rb +17 -0
  117. data/db/migrate/20221109010726_create_maquina_plans.rb +13 -0
  118. data/db/migrate/20221113000409_create_maquina_users.rb +19 -0
  119. data/db/migrate/20221113020108_create_maquina_used_passwords.rb +10 -0
  120. data/db/migrate/20221115223414_create_maquina_active_sessions.rb +15 -0
  121. data/db/migrate/20230201203922_create_maquina_invitations.rb +12 -0
  122. data/db/schema.rb +1 -0
  123. data/lib/generators/maquina/install_generator.rb +32 -0
  124. data/lib/generators/maquina/install_templates/install_templates_generator.rb +31 -0
  125. data/lib/generators/maquina/tailwind_config/tailwind_config_generator.rb +11 -0
  126. data/lib/generators/maquina/tailwind_config/templates/app/assets/config/maquina/tailwind.config.js.tt +68 -0
  127. data/lib/generators/maquina/templates/config/initializers/maquina.rb +3 -0
  128. data/lib/maquina/engine.rb +17 -0
  129. data/lib/maquina/version.rb +3 -0
  130. data/lib/maquina.rb +48 -0
  131. data/lib/tasks/install.rake +19 -0
  132. data/lib/tasks/maquina_tasks.rake +4 -0
  133. data/lib/tasks/tailwind.rake +25 -0
  134. metadata +456 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e5dffb067d8cdaf6d56c9777275907b321ec48188950945521a44d4d48fd92a2
4
+ data.tar.gz: 28a3e313221bd3f2e465ba565e5e493ba3ccc419512c7f5f59d80353a0d45168
5
+ SHA512:
6
+ metadata.gz: 2b4517a5605cd8245948afec893c75b60fdc522d56982d76928726fdcf797e2552a026321f2e24d2ab8a7387e8de01158a10065300d4914f5d07cf4b1df0c3d7
7
+ data.tar.gz: cb442f6712c58691771d8b01f99d22038c9491b92c5cf324860d5e250b692d749c67254184194de8d2b0132fb9339d32f17ebe6976c9eacd30a903a87bf098a7
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2022 Mario Alberto Chávez
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # Maquina
2
+ Short description and motivation.
3
+
4
+ ## Usage
5
+ How to use my plugin.
6
+
7
+ ## Installation
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem "maquina"
12
+ ```
13
+
14
+ And then execute:
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+ ```bash
21
+ $ gem install maquina
22
+ ```
23
+
24
+ ## Contributing
25
+ Contribution directions go here.
26
+
27
+ ## License
28
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ load "rails/tasks/statistics.rake"
7
+
8
+ require "bundler/gem_tasks"
@@ -0,0 +1,18 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg version="1.1" viewBox="0 0 486 486" xmlns="http://www.w3.org/2000/svg">
3
+ <title>Maquina</title>
4
+ <defs>
5
+ <linearGradient id="a" x1="21.152%" y1="21.848%" y2="93.468%">
6
+ <stop stop-color="#50B1FD" stop-opacity=".96" offset="0"/>
7
+ <stop stop-color="#DE77DE" stop-opacity=".96" offset="1"/>
8
+ </linearGradient>
9
+ </defs>
10
+ <g fill="none" fill-rule="evenodd">
11
+ <g transform="translate(-13 -13)">
12
+ <g transform="translate(13 13)">
13
+ <circle cx="243" cy="243" r="243" fill="url(#a)"/>
14
+ <path d="m238.9 47.002 3.4301 0.016598v30.431c-108.78 8.7223-163.18 71.895-163.18 189.52l54.567-31.709c3.7144-56.281 50.542-100.78 107.76-100.78 10.162 0 19.996 1.4034 29.319 4.0271 0.78425-57.904 1.3072-88.259 1.5685-91.068 161.58 36.443 197.13 192.94 138.54 291.1-4.3817-2.5298-13.063-7.5417-26.354-15.216 46.838-98.57 19.325-177.26-82.539-236.07l0.18734 65.903c28.537 19.437 47.276 52.193 47.276 89.325 0 30.25-12.436 57.595-32.475 77.201 49.541 28.346 75.468 43.309 77.781 44.887-110.21 123.65-264.32 78.878-321.77-19.957 4.3369-2.6059 12.929-7.7686 26.085-15.673 63.504 88.753 145.67 102.84 246.49 42.26l-57.118-31.449c-14.207 6.876-30.149 10.731-46.991 10.731-50.271 0-92.52-34.347-104.56-80.855-49.09 27.458-74.899 41.768-77.424 42.929-48.243-154.99 63.879-262.81 175.97-265.5l3.4312-0.04945zm3.0839 149.47c-25.681 0-46.5 20.819-46.5 46.5s20.819 46.5 46.5 46.5c25.681 0 46.5-20.819 46.5-46.5s-20.819-46.5-46.5-46.5z" fill="#fff"/>
15
+ </g>
16
+ </g>
17
+ </g>
18
+ </svg>
@@ -0,0 +1,4 @@
1
+ // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
2
+ import "@hotwired/turbo-rails"
3
+
4
+ import "controllers"
@@ -0,0 +1,29 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import { useTransition } from "stimulus-use"
3
+
4
+ /**
5
+ * Alert Controller
6
+ *
7
+ * Cuando hay información en el objeto Flash de Rails se muestra con una animación
8
+ * el mensaje y desaparece solo después de 10 segundos o cerrándolo con la X.
9
+ */
10
+
11
+ export default class extends Controller {
12
+ static targets = ["alert"];
13
+
14
+ connect() {
15
+ let element = this.element;
16
+ if (this.hasAlertTarget) {
17
+ element = this.alertTarget
18
+ }
19
+ useTransition(this, { element: element })
20
+
21
+ this.enter()
22
+
23
+ setTimeout(this.close.bind(this), 10000)
24
+ }
25
+
26
+ close() {
27
+ this.leave()
28
+ }
29
+ }
@@ -0,0 +1,9 @@
1
+ import { Application } from "@hotwired/stimulus"
2
+
3
+ const application = Application.start()
4
+
5
+ // Configure Stimulus development experience
6
+ application.debug = false
7
+ window.Stimulus = application
8
+
9
+ export { application }
@@ -0,0 +1,60 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["fileInput", "container", "preview", "placeholder"]
5
+ static values = { maxSize: Number, image: String, validationMessage: String }
6
+
7
+ connect() {
8
+ if (this.hasFileInputTarget) {
9
+ this.fileInputTarget.addEventListener("change", this.fileSelected.bind(this))
10
+ }
11
+ }
12
+
13
+ disconnect() {
14
+ if (this.hasFileInputTarget) {
15
+ this.fileInputTarget.removeEventListener("change", this.fileSelected.bind(this))
16
+ }
17
+ }
18
+
19
+ select(event) {
20
+ event.preventDefault();
21
+ this.fileInputTarget.click();
22
+ }
23
+
24
+ fileSelected(event) {
25
+ if (event.target.files.length > 0) {
26
+ this.containerTarget.querySelectorAll(".help-error").forEach(error => error.remove())
27
+ this.validateFile(event.target.files[0])
28
+ }
29
+ }
30
+
31
+ validateFile(file) {
32
+ if (file.size > this.maxSizeValue) {
33
+ this.fileInputTarget.value = ""
34
+ this.addError()
35
+ this.clearPreview()
36
+ } else {
37
+ this.loadPreview(file)
38
+ }
39
+ }
40
+
41
+ addError() {
42
+ const messageNode = document.createElement("p");
43
+ messageNode.classList.add("help", "help-error");
44
+ messageNode.innerText = this.validationMessageValue;
45
+
46
+ this.containerTarget.appendChild(messageNode)
47
+ }
48
+
49
+ clearPreview() {
50
+ this.previewTarget.classList.add("hidden")
51
+ this.placeholderTarget.classList.remove("hidden")
52
+ this.previewTarget.setAttribute("src", "")
53
+ }
54
+
55
+ loadPreview(file) {
56
+ this.previewTarget.setAttribute("src", URL.createObjectURL(file))
57
+ this.previewTarget.classList.remove("hidden")
58
+ this.placeholderTarget.classList.add("hidden")
59
+ }
60
+ }
@@ -0,0 +1,11 @@
1
+ // Import and register all your controllers from the importmap under controllers/*
2
+
3
+ import { application } from "controllers/application"
4
+
5
+ // Eager load all controllers defined in the import map under controllers/**/*_controller
6
+ import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
7
+ eagerLoadControllersFrom("controllers", application)
8
+
9
+ // Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!)
10
+ // import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading"
11
+ // lazyLoadControllersFrom("controllers", application)
@@ -0,0 +1,31 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import { useClickOutside, useTransition } from "stimulus-use"
3
+
4
+ export default class extends Controller {
5
+ static targets = ["menu", "open", "close"]
6
+
7
+ connect() {
8
+ useClickOutside(this);
9
+ useTransition(this, { element: this.menuTarget })
10
+ }
11
+
12
+ toggle() {
13
+ this.toggleTransition();
14
+ }
15
+
16
+ clickOutside() {
17
+ if (!this.menuTarget.classList.contains("hidden")) {
18
+ this.toggleTransition();
19
+ }
20
+ }
21
+
22
+ enter() {
23
+ this.openTarget.classList.toggle("hidden")
24
+ this.closeTarget.classList.toggle("hidden")
25
+ }
26
+
27
+ leave() {
28
+ this.openTarget.classList.toggle("hidden")
29
+ this.closeTarget.classList.toggle("hidden")
30
+ }
31
+ }
@@ -0,0 +1,39 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import { useTransition } from "stimulus-use"
3
+
4
+ export default class extends Controller {
5
+ static targets = ["content", "container", "frame"]
6
+
7
+ connect() {
8
+ useTransition(this, { element: this.contentTarget })
9
+
10
+ this.resultCallback = this.modalResult.bind(this)
11
+ addEventListener("turbo:submit-end", this.resultCallback)
12
+ }
13
+
14
+ disconnect() {
15
+ removeEventListener("turbo:submit-end", this.resultCallback)
16
+ }
17
+
18
+ modalResult(event) {
19
+ if (event.detail.fetchResponse.response.status == 202) {
20
+ this.toggleModal(event)
21
+ }
22
+ }
23
+
24
+ toggleModal(event) {
25
+ event.preventDefault()
26
+
27
+ if (this.hasFrameTarget && this.contentTarget.classList.contains("hidden")) {
28
+ let url = event["detail"]["src"] || this.element.dataset["frameSrc"]
29
+ this.frameTarget.setAttribute("src", url)
30
+ } else if (!this.contentTarget.classList.contains("hidden")) {
31
+ this.frameTarget.setAttribute("src", "")
32
+ this.frameTarget.innerText = ""
33
+ }
34
+
35
+ document.documentElement.classList.toggle("overflow-hidden")
36
+ this.containerTarget.classList.toggle("hidden")
37
+ this.toggleTransition()
38
+ }
39
+ }
@@ -0,0 +1,15 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ import { useDispatch } from "stimulus-use";
3
+
4
+ export default class extends Controller {
5
+ static targets = [];
6
+
7
+ connect() {
8
+ useDispatch(this)
9
+ }
10
+
11
+ open(event) {
12
+ event.preventDefault();
13
+ this.dispatch("toggle", {src: event.target.getAttribute("href")});
14
+ }
15
+ }
@@ -0,0 +1,17 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ import { useClickOutside, useTransition } from "stimulus-use"
3
+
4
+ export default class extends Controller {
5
+ static targets = ["menu"];
6
+
7
+ connect() {
8
+ useClickOutside(this);
9
+ useTransition(this, { element: this.menuTarget })
10
+ }
11
+
12
+ clickOutside() {
13
+ if (!this.menuTarget.classList.contains("hidden")) {
14
+ this.toggleTransition();
15
+ }
16
+ }
17
+ }
@@ -0,0 +1,11 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = []
5
+
6
+ clear(event) {
7
+ if (!event.target.value) {
8
+ this.element.submit()
9
+ }
10
+ }
11
+ }
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,102 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ /* @layer base { */
6
+ :root {
7
+ --color-base: 31 27 54;
8
+ --color-accented: 37 99 235;
9
+ --color-inverted: 255 255 255;
10
+ --color-accented-hover: 59 130 246;
11
+ --color-muted: 55 65 81;
12
+ --color-dimmed: 75 85 99;
13
+ --color-error: 220 38 38;
14
+
15
+ --color-border-base: 209 213 219;
16
+ --color-border-accented: 37 99 235;
17
+ }
18
+
19
+ .label {
20
+ @apply text-sm font-medium text-skin-muted;
21
+ }
22
+
23
+ .input {
24
+ @apply appearance-none rounded-md border border-skin-base px-3 py-2 placeholder-gray-400 shadow-sm focus:border-skin-accented focus:outline-none focus:ring-skin-accented sm:text-sm;
25
+ }
26
+
27
+ .link {
28
+ @apply font-medium text-skin-accented hover:text-skin-accented-hover;
29
+ }
30
+
31
+ .check {
32
+ @apply border-skin-base h-4 w-4 text-skin-accented focus:ring-skin-accented rounded;
33
+ }
34
+
35
+ .button {
36
+ @apply inline-flex justify-center py-2 px-4 border border-skin-base rounded-md shadow-sm text-sm font-medium text-skin-muted bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-skin-accented;
37
+ }
38
+
39
+ .control-error .label {
40
+ @apply text-skin-error;
41
+ }
42
+
43
+ .control-error .input {
44
+ @apply border-skin-error focus:border-skin-error focus:ring-skin-error;
45
+ }
46
+
47
+ .help {
48
+ @apply mt-2 text-sm text-skin-dimmed;
49
+ }
50
+
51
+ .help-error {
52
+ @apply text-skin-error;
53
+ }
54
+
55
+ .button[disabled] {
56
+ @apply bg-gray-50 text-skin-dimmed cursor-default;
57
+ }
58
+
59
+ .button-accented {
60
+ @apply rounded-md border border-transparent bg-skin-accented py-2 px-4 text-sm font-medium text-skin-inverted shadow-sm hover:bg-skin-accented-hover focus:outline-none focus:ring-2 focus:ring-skin-accented focus:ring-offset-2;
61
+ }
62
+
63
+ input[type="search"] {
64
+ @apply block w-full rounded-md border border-skin-base pl-10 leading-5 placeholder-gray-400 focus:placeholder-gray-300 focus:outline-none focus:ring-1 focus:ring-skin-accented focus:border-skin-accented sm:text-sm;
65
+ }
66
+
67
+ .desktop-menu {
68
+ @apply hidden lg:ml-6 lg:flex lg:space-x-8;
69
+ }
70
+
71
+ .desktop-menu-item {
72
+ @apply font-sans uppercase border-transparent text-skin-dimmed underline-offset-4 decoration-2 hover:underline hover:decoration-skin-accented hover:text-skin-muted inline-flex items-center px-1 pt-1 text-sm font-medium;
73
+ }
74
+
75
+ .desktop-menu-item__active {
76
+ @apply decoration-skin-accented underline text-skin-base;
77
+ }
78
+
79
+ .mobile-menu {
80
+ @apply pt-2 pb-3 space-y-1;
81
+ }
82
+
83
+ .mobile-menu-item {
84
+ @apply font-sans border-transparent text-skin-dimmed hover:bg-gray-200 hover:border-gray-300 hover:text-skin-muted block pl-3 pr-4 py-2 border-l-4 text-base font-medium;
85
+ }
86
+
87
+ .mobile-menu-item__active {
88
+ @apply bg-skin-accented-hover border-skin-accented text-skin-accented hover:text-skin-accented hover:bg-skin-accented-hover hover:border-skin-accented;
89
+ }
90
+
91
+ .desktop-profile-menu-item {
92
+ @apply block w-full text-left font-sans px-4 py-2 text-sm text-skin-dimmed hover:bg-gray-200 hover:text-skin-muted;
93
+ }
94
+
95
+ .mobile-profile-menu-item {
96
+ @apply block font-sans px-4 py-2 text-base font-medium text-skin-dimmed hover:text-skin-muted hover:bg-gray-200;
97
+ }
98
+
99
+ .mobile-button {
100
+ @apply inline-flex items-center justify-center p-2 rounded-md text-skin-dimmed hover:text-skin-muted hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-skin-accented;
101
+ }
102
+ /* } */
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Maquina
4
+ module Authenticate
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ helper_method :signed_in?, :current_user
9
+ end
10
+
11
+ def authenticate!
12
+ load_session
13
+
14
+ redirect_to maquina.new_sessions_path if !signed_in?
15
+ end
16
+
17
+ def signed_in?
18
+ load_session if Maquina::Current.active_session.blank?
19
+
20
+ Maquina::Current.signed_in?
21
+ end
22
+
23
+ def current_user
24
+ load_session if Maquina::Current.active_session.blank?
25
+
26
+ Maquina::Current.user
27
+ end
28
+
29
+ private
30
+
31
+ def load_session
32
+ active_session_id = session["--active_session"]
33
+ return if active_session_id.blank?
34
+
35
+ active_session = ActiveSession.eager_load(:user).find_by(id: active_session_id)
36
+ return if active_session.blank?
37
+
38
+ Maquina::Current.active_session = active_session
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Maquina
4
+ module Create
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ def create(&block)
9
+ authorize! with: policy_class if policy_class.present?
10
+
11
+ @resource ||= begin
12
+ resource = resource_class.new(resource_secure_params)
13
+ resource.save
14
+
15
+ resource
16
+ end
17
+
18
+ @status = @resource.errors.empty? ? :created : :unprocessable_entity
19
+ response.status = @status
20
+ set_flash_message(@status)
21
+
22
+ dual_action_response(@resource, &block)
23
+ end
24
+ alias_method :create!, :create
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Maquina
4
+ module Destroy
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ def destroy(&block)
9
+ @resource ||= begin
10
+ scope = resource_class
11
+ # TODO: Implement policy authorization (ActionPolicy)
12
+ # scope = scope.where(organization)
13
+ # TODO: Implement filtering by organization
14
+ scope.find_by!(find_by_param => params[:id])
15
+ end
16
+
17
+ @resource.destroy
18
+
19
+ status = :no_content
20
+ response.status = status
21
+ set_flash_message(status)
22
+
23
+ dual_action_response(@resource, &block)
24
+ end
25
+ alias_method :destroy!, :destroy
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Maquina
4
+ module Edit
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ def edit(&block)
9
+ @resource ||= begin
10
+ scope = resource_class
11
+ # TODO: Implement filtering by organization
12
+ # scope = scope.where(organization)
13
+ scope = yield(scope) if block.present?
14
+
15
+ resource = scope.find_by!(find_by_param => params[:id])
16
+
17
+ authorize! resource, with: policy_class if policy_class.present?
18
+
19
+ resource
20
+ end
21
+
22
+ respond_to do |format|
23
+ format.html
24
+ end
25
+ end
26
+ alias_method :edit!, :edit
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Maquina
4
+ module Index
5
+ extend ActiveSupport::Concern
6
+ include Pagy::Backend
7
+
8
+ included do
9
+ def index(&block)
10
+ authorize! with: policy_class if policy_class.present?
11
+
12
+ @collection ||= begin
13
+ scope = resource_class.order(updated_at: :desc)
14
+ scope = authorized_scope(scope) if policy_class.present?
15
+
16
+ search_value = params[:q]&.strip
17
+ scope = scope.search_full(search_value) if search_value.present?
18
+
19
+ scope = yield(scope) if block.present?
20
+
21
+ @pagination, record = pagy(scope)
22
+ record
23
+ end
24
+
25
+ respond_to do |format|
26
+ format.html { render :index }
27
+ format.json { render json: collection }
28
+ end
29
+ end
30
+ alias_method :index!, :index
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Maquina
4
+ module New
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ def new(&block)
9
+ authorize! with: policy_class if policy_class.present?
10
+
11
+ # TODO: Implement filtering by organization
12
+ @resource = resource_class.new
13
+ @resource = yield(@resource) if block.present?
14
+
15
+ respond_to do |format|
16
+ format.html
17
+ end
18
+ end
19
+ alias_method :new!, :new
20
+ end
21
+ end
22
+ end