lato_users 3.0.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 (39) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +67 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/config/lato_users_manifest.js +2 -0
  6. data/app/assets/javascripts/lato_users/application.js +1 -0
  7. data/app/assets/javascripts/lato_users/controllers/lato_users_form_controller.js +27 -0
  8. data/app/assets/stylesheets/lato_users/application.scss +15 -0
  9. data/app/controllers/lato_users/application_controller.rb +20 -0
  10. data/app/controllers/lato_users/invitations_controller.rb +39 -0
  11. data/app/controllers/lato_users/users_controller.rb +107 -0
  12. data/app/helpers/lato_users/application_helper.rb +4 -0
  13. data/app/helpers/lato_users/invitations_helper.rb +25 -0
  14. data/app/helpers/lato_users/users_helper.rb +15 -0
  15. data/app/jobs/lato_users/application_job.rb +4 -0
  16. data/app/mailers/lato_users/application_mailer.rb +6 -0
  17. data/app/views/lato_users/invitations/_form.html.erb +23 -0
  18. data/app/views/lato_users/invitations/index.html.erb +20 -0
  19. data/app/views/lato_users/invitations/new.html.erb +14 -0
  20. data/app/views/lato_users/users/_card_account_informations.html.erb +29 -0
  21. data/app/views/lato_users/users/_card_email_verification.html.erb +37 -0
  22. data/app/views/lato_users/users/_card_privacy_and_terms.html.erb +70 -0
  23. data/app/views/lato_users/users/_card_security.html.erb +162 -0
  24. data/app/views/lato_users/users/_form.html.erb +98 -0
  25. data/app/views/lato_users/users/edit.html.erb +13 -0
  26. data/app/views/lato_users/users/index.html.erb +24 -0
  27. data/app/views/lato_users/users/new.html.erb +13 -0
  28. data/app/views/lato_users/users/show.html.erb +12 -0
  29. data/config/importmap.rb +2 -0
  30. data/config/locales/en.yml +61 -0
  31. data/config/locales/it.yml +60 -0
  32. data/config/routes.rb +14 -0
  33. data/db/migrate/20250328072343_add_lato_users_admin_to_lato_user.rb +5 -0
  34. data/lib/lato_users/config.rb +10 -0
  35. data/lib/lato_users/engine.rb +13 -0
  36. data/lib/lato_users/version.rb +3 -0
  37. data/lib/lato_users.rb +15 -0
  38. data/lib/tasks/lato_users_tasks.rake +11 -0
  39. metadata +110 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b87e863ea43eda9714f7f1322e2765405ea8dce78ef8782aadff894dbb459d77
4
+ data.tar.gz: 2fe7688ff2e8672ccb38e78bb69d69cf73b97f9edb4b33783ea2ae38cb7d5a11
5
+ SHA512:
6
+ metadata.gz: 699e2b36913d0904525a54fb7e424b594a8924e8db9c462b1d6e11ea7ce3cca8fc09d85f4139825584a2838740fb42bd0731f6d1bcaa154be3f156e73e084211
7
+ data.tar.gz: 2dca85f420d6ad80e0f5528471c044cd9760ff12e8e11caef54842fd7a2f08eb8769c24669307c1cc0e63d4f93f76393c603fa98f9a309026ccf38fa2afe5d57
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright Gregorio Galante
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,67 @@
1
+ # Lato Users
2
+
3
+ Manage application users on Lato projects.
4
+
5
+ ## Installation
6
+ Add required dependencies to your application's Gemfile:
7
+
8
+ ```ruby
9
+ # Use lato as application panel
10
+ gem "lato"
11
+ gem "lato_users"
12
+ ```
13
+
14
+ Install gem and run required tasks:
15
+
16
+ ```bash
17
+ $ bundle
18
+ $ rails lato_users:install:application
19
+ $ rails lato_users:install:migrations
20
+ $ rails db:migrate
21
+ ```
22
+
23
+ Mount lato users routes on the **config/routes.rb** file:
24
+
25
+ ```ruby
26
+ Rails.application.routes.draw do
27
+ mount LatoUsers::Engine => "/lato-users"
28
+ # ....
29
+ end
30
+ ```
31
+
32
+ Import Lato Scss on **app/assets/stylesheets/application.scss** file:
33
+ ```scss
34
+ @import 'lato_users/application';
35
+
36
+ // ....
37
+ ```
38
+
39
+ Import Lato Users Js on **app/javascript/application.js** file:
40
+ ```js
41
+ import "lato_users/application";
42
+
43
+ // ....
44
+ ```
45
+
46
+ ## Development
47
+
48
+ Clone repository, install dependencies, run migrations and start:
49
+
50
+ ```shell
51
+ $ git clone https://github.com/Lato-GAM/lato_users
52
+ $ cd lato_users
53
+ $ bundle
54
+ $ rails db:migrate
55
+ $ rails db:seed
56
+ $ foreman start -f Procfile.dev
57
+ ```
58
+
59
+ ## Publish
60
+
61
+ ```shell
62
+ $ ruby ./bin/publish.rb
63
+ ```
64
+
65
+ ## License
66
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
67
+
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,2 @@
1
+ //= link_directory ../stylesheets/lato_users .css
2
+ //= link_tree ../javascripts/lato_users .js
@@ -0,0 +1 @@
1
+ console.log("Lato Users JS loaded");
@@ -0,0 +1,27 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = [
5
+ "password",
6
+ "passwordConfirmation",
7
+ "generatedPassword"
8
+ ]
9
+
10
+ connect() {
11
+
12
+ }
13
+
14
+ generatePassword(event) {
15
+ event.preventDefault()
16
+ const length = 12
17
+ const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*"
18
+ let retVal = ""
19
+ for (let i = 0, n = charset.length; i < length; ++i) {
20
+ retVal += charset.charAt(Math.floor(Math.random() * n))
21
+ }
22
+
23
+ this.passwordTarget.value = retVal
24
+ this.passwordConfirmationTarget.value = retVal
25
+ this.generatedPasswordTarget.value = retVal
26
+ }
27
+ }
@@ -0,0 +1,15 @@
1
+ .controller-users {
2
+ .lato-index-desk {
3
+ .table {
4
+ tbody, thead {
5
+ td:not(:last-child), th:not(:last-child) {
6
+ width: 25%;
7
+ }
8
+
9
+ td:last-child {
10
+ text-align: right;
11
+ }
12
+ }
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,20 @@
1
+ module LatoUsers
2
+ class ApplicationController < Lato::ApplicationController
3
+ layout 'lato/application'
4
+ before_action :authenticate_session
5
+ before_action :authenticate_lato_users_admin
6
+ before_action { active_sidebar(:lato_users); active_navbar(:lato_users) }
7
+
8
+ def index
9
+ redirect_to lato_users.users_path
10
+ end
11
+
12
+ protected
13
+
14
+ def authenticate_lato_users_admin
15
+ return true if @session.user&.lato_users_admin
16
+
17
+ redirect_to lato.root_path, alert: 'You have not access to this section.'
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,39 @@
1
+ module LatoUsers
2
+ class InvitationsController < ApplicationController
3
+ def index
4
+ @invitations = lato_index_collection(
5
+ Lato::Invitation.all,
6
+ columns: %i[email accepted_at created_at actions],
7
+ searchable_columns: %i[email],
8
+ sortable_columns: %i[email accepted_at created_at],
9
+ pagination: true,
10
+ default_sort_by: "created_at|desc"
11
+ )
12
+ end
13
+
14
+ def new
15
+ @invitation = Lato::Invitation.new
16
+ end
17
+
18
+ def create
19
+ @invitation = Lato::Invitation.new(invitation_params.merge(inviter_lato_user_id: @session.user_id))
20
+ if @invitation.save
21
+ redirect_to invitations_path, notice: I18n.t('lato_users.invitation_created')
22
+ else
23
+ render 'new'
24
+ end
25
+ end
26
+
27
+ def destroy
28
+ @invitation = Lato::Invitation.find(params[:id])
29
+ @invitation.destroy
30
+ redirect_to invitations_path, notice: I18n.t('lato_users.invitation_destroyed')
31
+ end
32
+
33
+ private
34
+
35
+ def invitation_params
36
+ params.require(:invitation).permit(:email)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,107 @@
1
+ module LatoUsers
2
+ class UsersController < ApplicationController
3
+ before_action :load_available_admin_permissions
4
+
5
+ def index
6
+ @users = lato_index_collection(
7
+ Lato::User.all,
8
+ columns: %i[id email first_name last_name locale actions],
9
+ searchable_columns: %i[email first_name last_name],
10
+ sortable_columns: %i[id email first_name last_name],
11
+ pagination: true,
12
+ default_sort_by: "id|asc"
13
+ )
14
+ end
15
+
16
+ def show
17
+ @user = Lato::User.find(params[:id])
18
+ end
19
+
20
+ def new
21
+ @user = Lato::User.new
22
+ end
23
+
24
+ def create
25
+ @user = Lato::User.new(user_params.merge(
26
+ accepted_privacy_policy_version: Lato.config.legal_privacy_policy_version,
27
+ accepted_terms_and_conditions_version: Lato.config.legal_terms_and_conditions_version
28
+ ))
29
+ if @user.save
30
+ redirect_to users_path
31
+ else
32
+ render 'new'
33
+ end
34
+ end
35
+
36
+ def edit
37
+ @user = Lato::User.find(params[:id])
38
+ end
39
+
40
+ def update
41
+ @user = Lato::User.find(params[:id])
42
+ if @user.update(user_params)
43
+ redirect_to(params[:redirect_to] || users_path)
44
+ else
45
+ render 'edit'
46
+ end
47
+ end
48
+
49
+ def destroy
50
+ if params[:id]&.to_i == @session.user_id
51
+ redirect_to users_path, alert: I18n.t('lato_users.cannot_delete_self')
52
+ return
53
+ end
54
+
55
+ @user = Lato::User.find(params[:id])
56
+ @user.destroy
57
+ redirect_to users_path
58
+ end
59
+
60
+ def resend_verification_email
61
+ @user = Lato::User.find(params[:id])
62
+ if @user.request_verify_email
63
+ redirect_to user_path(@user), notice: I18n.t('lato_users.verification_email_sent')
64
+ else
65
+ redirect_to user_path(@user), alert: @user.errors.full_messages.join(', ')
66
+ end
67
+ end
68
+
69
+ def manual_verify_email
70
+ @user = Lato::User.find(params[:id])
71
+ if @user.update(email_verified_at: Time.current)
72
+ redirect_to user_path(@user), notice: I18n.t('lato_users.email_manually_verified')
73
+ else
74
+ redirect_to user_path(@user), alert: @user.errors.full_messages.join(', ')
75
+ end
76
+ end
77
+
78
+ def revoke_email_verification
79
+ @user = Lato::User.find(params[:id])
80
+ if @user.update(email_verified_at: nil)
81
+ redirect_to user_path(@user), notice: I18n.t('lato_users.email_verification_revoked')
82
+ else
83
+ redirect_to user_path(@user), alert: @user.errors.full_messages.join(', ')
84
+ end
85
+ end
86
+
87
+ def force_update_password
88
+ @user = Lato::User.find(params[:id])
89
+ new_password = SecureRandom.hex(8)
90
+ if @user.update(password: new_password, password_confirmation: new_password)
91
+ redirect_to user_path(@user), notice: I18n.t('lato_users.password_updated', password: new_password)
92
+ else
93
+ redirect_to user_path(@user), alert: I18n.t('lato_users.password_update_failed')
94
+ end
95
+ end
96
+
97
+ private
98
+
99
+ def user_params
100
+ params.require(:user).permit(:email, :first_name, :last_name, :password, :password_confirmation, @available_admin_permissions)
101
+ end
102
+
103
+ def load_available_admin_permissions
104
+ @available_admin_permissions ||= Lato::User.columns.map(&:name).select { |c| c.start_with?('lato_') && c.end_with?('_admin') }
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,4 @@
1
+ module LatoUsers
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,25 @@
1
+ module LatoUsers
2
+ module InvitationsHelper
3
+ def lato_invitation_created_at(invitation)
4
+ I18n.l(invitation.created_at, format: :short)
5
+ end
6
+
7
+ def lato_invitation_accepted_at(invitation)
8
+ if invitation.accepted_at.present?
9
+ I18n.l(invitation.accepted_at, format: :short)
10
+ else
11
+ ' - '
12
+ end
13
+ end
14
+
15
+ def lato_invitation_actions(invitation)
16
+ content_tag(:div, class: 'btn-group btn-group-sm') do
17
+ if invitation.accepted_at.nil?
18
+ concat link_to(I18n.t('lato_users.cta_delete'), lato_users.invitation_path(invitation), class: 'btn btn-danger', data: { turbo_confirm: I18n.t('lato_users.cta_delete_confirm'), turbo_method: :delete, turbo_frame: '_top' })
19
+ else
20
+ concat content_tag(:button, I18n.t('lato_users.cta_delete'), class: 'btn btn-danger', disabled: true, title: I18n.t('lato_users.cannot_delete_accepted_invitation'))
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,15 @@
1
+ module LatoUsers
2
+ module UsersHelper
3
+ def lato_user_actions(user, show: true)
4
+ content_tag(:div, class: 'btn-group btn-group-sm') do
5
+ concat link_to(I18n.t('lato_users.cta_show'), lato_users.user_path(user), class: 'btn btn-secondary', data: { turbo_frame: '_top' }) if show
6
+ concat link_to(I18n.t('lato_users.cta_edit'), lato_users.edit_user_path(user, redirect_to: request.fullpath), class: 'btn btn-primary', data: { lato_action_target: 'trigger', turbo_frame: dom_id(user, 'form'), action_title: I18n.t('lato_users.edit_user') })
7
+ if user.id == @session.user_id
8
+ concat content_tag(:button, I18n.t('lato_users.cta_delete'), class: 'btn btn-danger', disabled: true, title: I18n.t('lato_users.cannot_delete_self'))
9
+ else
10
+ concat link_to(I18n.t('lato_users.cta_delete'), lato_users.user_path(user), class: 'btn btn-danger', data: { turbo_confirm: I18n.t('lato_users.cta_delete_confirm'), turbo_method: :delete, turbo_frame: '_top' })
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,4 @@
1
+ module LatoUsers
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module LatoUsers
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: "from@example.com"
4
+ layout "mailer"
5
+ end
6
+ end
@@ -0,0 +1,23 @@
1
+ <%
2
+ invitation ||= Lato::Invitation.new
3
+ %>
4
+
5
+ <%= turbo_frame_tag dom_id(invitation, 'form') do %>
6
+ <%= form_with model: invitation, url: invitation.persisted? ? lato_users.invitation_path(invitation) : lato_users.invitations_path, data: { turbo_frame: '_self', controller: 'lato-form' } do |form| %>
7
+ <%= lato_form_notices class: %w[mb-3] %>
8
+ <%= lato_form_errors invitation, class: %w[mb-3] %>
9
+
10
+ <div class="row">
11
+ <div class="col col-12 mb-3">
12
+ <%= lato_form_item_label form, :email %>
13
+ <%= lato_form_item_input_email form, :email, required: true %>
14
+ </div>
15
+ </div>
16
+
17
+ <div class="row">
18
+ <div class="col-12 text-end">
19
+ <%= form.submit I18n.t('lato_users.cta_confirm'), class: 'btn btn-primary' %>
20
+ </div>
21
+ </div>
22
+ <% end %>
23
+ <% end %>
@@ -0,0 +1,20 @@
1
+ <%= lato_page_head I18n.t('lato_users.invitations'), [
2
+ { label: I18n.t('lato_users.users'), path: lato_users.users_path },
3
+ { label: I18n.t('lato_users.invitations') }
4
+ ] %>
5
+
6
+ <div class="card mb-4">
7
+ <div class="card-body">
8
+ <%= lato_index @invitations,
9
+ custom_actions: {
10
+ create: {
11
+ path: lato_users.new_invitation_path,
12
+ icon: 'bi bi-plus',
13
+ aria_label: I18n.t('lato_users.add_invitation'),
14
+ title: I18n.t('lato_users.add_invitation'),
15
+ data: { lato_action_target: 'trigger', turbo_frame: dom_id(Lato::Invitation.new, 'form'), action_title: I18n.t('lato_users.add_invitation'), controller: 'lato-tooltip' },
16
+ }
17
+ }
18
+ %>
19
+ </div>
20
+ </div>
@@ -0,0 +1,14 @@
1
+ <%= lato_page_head I18n.t('lato_users.add_invitation'), [
2
+ { label: I18n.t('lato_users.users'), path: lato_users.users_path },
3
+ { label: I18n.t('lato_users.invitations'), path: lato_users.invitations_path },
4
+ { label: I18n.t('lato_users.add_invitation') }
5
+ ] %>
6
+
7
+ <div class="card mb-4">
8
+ <div class="card-header">
9
+ <h2 class="fs-4 mb-0"><%= I18n.t('lato_users.add_invitation') %></h2>
10
+ </div>
11
+ <div class="card-body">
12
+ <%= render 'form', invitation: @invitation %>
13
+ </div>
14
+ </div>
@@ -0,0 +1,29 @@
1
+ <div class="card mb-4">
2
+ <div class="card-header d-flex align-items-center justify-content-between">
3
+ <h2 class="fs-4 mb-0"><%= I18n.t('lato_users.account_informations') %></h2>
4
+ <%= lato_user_actions(@user, show: false) %>
5
+ </div>
6
+ <div class="card-body">
7
+ <div class="row">
8
+ <div class="col col-12 col-md-6 col-lg-3 mb-3">
9
+ <label class="form-label" for="first_name"><%= I18n.t('activerecord.attributes.lato/user.first_name') %></label>
10
+ <input type="text" id="first_name" class="form-control" value="<%= @user.first_name %>" disabled>
11
+ </div>
12
+
13
+ <div class="col col-12 col-md-6 col-lg-3 mb-3">
14
+ <label class="form-label" for="last_name"><%= I18n.t('activerecord.attributes.lato/user.last_name') %></label>
15
+ <input type="text" id="last_name" class="form-control" value="<%= @user.last_name %>" disabled>
16
+ </div>
17
+
18
+ <div class="col col-12 col-md-6 col-lg-3 mb-3">
19
+ <label class="form-label" for="email"><%= I18n.t('activerecord.attributes.lato/user.email') %></label>
20
+ <input type="text" id="email" class="form-control" value="<%= @user.email %>" disabled>
21
+ </div>
22
+
23
+ <div class="col col-12 col-md-6 col-lg-3 mb-3">
24
+ <label class="form-label" for="locale"><%= I18n.t('activerecord.attributes.lato/user.locale') %></label>
25
+ <input type="text" id="locale" class="form-control" value="<%= @user.locale %>" disabled>
26
+ </div>
27
+ </div>
28
+ </div>
29
+ </div>
@@ -0,0 +1,37 @@
1
+ <div class="card mb-4">
2
+ <div class="card-header">
3
+ <h2 class="fs-4 mb-0"><%= I18n.t('lato_users.email_verification') %></h2>
4
+ </div>
5
+ <div class="card-body">
6
+ <% if @user.email_verified_at %>
7
+ <div class="d-flex flex-column flex-md-row align-items-md-center justify-content-between p-3 bg-success bg-opacity-10 border border-success rounded">
8
+ <div class="d-flex align-items-center mb-3 mb-md-0">
9
+ <div class="me-3 text-success">
10
+ <i class="bi bi-check-circle-fill fs-4"></i>
11
+ </div>
12
+ <div>
13
+ <h5 class="fs-6 fw-bold text-success mb-0"><%= I18n.t('lato_users.email_verified') %></h5>
14
+ <small class="text-muted"><%= I18n.t('lato_users.email_verified_at', date: l(@user.email_verified_at, format: :long)) %></small>
15
+ </div>
16
+ </div>
17
+ <%= button_to I18n.t('lato_users.revoke_verification'), revoke_email_verification_user_path(@user), method: :patch, class: "btn btn-danger btn-sm", form: { data: { turbo_confirm: I18n.t('lato_users.are_you_sure') } } %>
18
+ </div>
19
+ <% else %>
20
+ <div class="d-flex flex-column flex-md-row align-items-md-center justify-content-between p-3 bg-warning bg-opacity-10 border border-warning rounded">
21
+ <div class="d-flex align-items-center mb-3 mb-md-0">
22
+ <div class="me-3 text-warning">
23
+ <i class="bi bi-exclamation-triangle-fill fs-4"></i>
24
+ </div>
25
+ <div>
26
+ <h5 class="fs-6 fw-bold text-warning mb-0"><%= I18n.t('lato_users.email_not_verified') %></h5>
27
+ <small class="text-muted"><%= I18n.t('lato_users.email_verification_required') %></small>
28
+ </div>
29
+ </div>
30
+ <div class="d-flex gap-2">
31
+ <%= button_to I18n.t('lato_users.resend_verification_email'), resend_verification_email_user_path(@user), method: :post, class: "btn btn-primary btn-sm" %>
32
+ <%= button_to I18n.t('lato_users.manual_verify_email'), manual_verify_email_user_path(@user), method: :patch, class: "btn btn-success btn-sm", form: { data: { turbo_confirm: I18n.t('lato_users.are_you_sure') } } %>
33
+ </div>
34
+ </div>
35
+ <% end %>
36
+ </div>
37
+ </div>
@@ -0,0 +1,70 @@
1
+ <div class="card mb-4">
2
+ <div class="card-header">
3
+ <h2 class="fs-4 mb-0"><%= I18n.t('lato_users.privacy_and_terms') %></h2>
4
+ </div>
5
+ <div class="card-body">
6
+ <div class="row">
7
+ <div class="col col-12 col-lg-6 mb-3 mb-lg-0">
8
+ <h5 class="fs-6 fw-bold mb-3"><%= I18n.t('lato_users.privacy_policy') %></h5>
9
+ <p class="mb-1">
10
+ <%= I18n.t('lato_users.accepted_version') %>:
11
+ <span class="fw-bold"><%= @user.accepted_privacy_policy_version %></span>
12
+ </p>
13
+ <p class="mb-3">
14
+ <%= I18n.t('lato_users.current_version') %>:
15
+ <span class="fw-bold"><%= Lato.config.legal_privacy_policy_version %></span>
16
+ </p>
17
+ <% if @user.accepted_privacy_policy_version < Lato.config.legal_privacy_policy_version %>
18
+ <div class="d-flex align-items-center p-3 bg-warning bg-opacity-10 border border-warning rounded">
19
+ <div class="me-3 text-warning">
20
+ <i class="bi bi-exclamation-triangle-fill fs-4"></i>
21
+ </div>
22
+ <div>
23
+ <h5 class="fs-6 fw-bold text-warning mb-0"><%= I18n.t('lato_users.version_outdated') %></h5>
24
+ </div>
25
+ </div>
26
+ <% else %>
27
+ <div class="d-flex align-items-center p-3 bg-success bg-opacity-10 border border-success rounded">
28
+ <div class="me-3 text-success">
29
+ <i class="bi bi-check-circle-fill fs-4"></i>
30
+ </div>
31
+ <div>
32
+ <h5 class="fs-6 fw-bold text-success mb-0"><%= I18n.t('lato_users.version_up_to_date') %></h5>
33
+ </div>
34
+ </div>
35
+ <% end %>
36
+ </div>
37
+
38
+ <div class="col col-12 col-lg-6">
39
+ <h5 class="fs-6 fw-bold mb-3"><%= I18n.t('lato_users.terms_and_conditions') %></h5>
40
+ <p class="mb-1">
41
+ <%= I18n.t('lato_users.accepted_version') %>:
42
+ <span class="fw-bold"><%= @user.accepted_terms_and_conditions_version %></span>
43
+ </p>
44
+ <p class="mb-3">
45
+ <%= I18n.t('lato_users.current_version') %>:
46
+ <span class="fw-bold"><%= Lato.config.legal_terms_and_conditions_version %></span>
47
+ </p>
48
+ <% if @user.accepted_terms_and_conditions_version < Lato.config.legal_terms_and_conditions_version %>
49
+ <div class="d-flex align-items-center p-3 bg-warning bg-opacity-10 border border-warning rounded">
50
+ <div class="me-3 text-warning">
51
+ <i class="bi bi-exclamation-triangle-fill fs-4"></i>
52
+ </div>
53
+ <div>
54
+ <h5 class="fs-6 fw-bold text-warning mb-0"><%= I18n.t('lato_users.version_outdated') %></h5>
55
+ </div>
56
+ </div>
57
+ <% else %>
58
+ <div class="d-flex align-items-center p-3 bg-success bg-opacity-10 border border-success rounded">
59
+ <div class="me-3 text-success">
60
+ <i class="bi bi-check-circle-fill fs-4"></i>
61
+ </div>
62
+ <div>
63
+ <h5 class="fs-6 fw-bold text-success mb-0"><%= I18n.t('lato_users.version_up_to_date') %></h5>
64
+ </div>
65
+ </div>
66
+ <% end %>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ </div>
@@ -0,0 +1,162 @@
1
+ <div class="card mb-4">
2
+ <div class="card-header">
3
+ <h2 class="fs-4 mb-0"><%= I18n.t('lato_users.security') %></h2>
4
+ </div>
5
+ <div class="card-body">
6
+ <% if Lato.config.authenticator_connection || Lato.config.webauthn_connection %>
7
+ <div class="row mb-4">
8
+ <div class="col-12">
9
+ <div class="list-group">
10
+ <% if Lato.config.authenticator_connection %>
11
+ <div class="list-group-item p-3">
12
+ <div class="row align-items-center">
13
+ <div class="col">
14
+ <span class="fw-bold"><%= I18n.t('lato_users.authenticator_connection') %></span>
15
+ </div>
16
+ <div class="col-auto">
17
+ <% if @user.authenticator_secret.present? %>
18
+ <span class="badge bg-success"><%= I18n.t('lato_users.connected') %></span>
19
+ <% else %>
20
+ <span class="badge bg-secondary"><%= I18n.t('lato_users.not_connected') %></span>
21
+ <% end %>
22
+ </div>
23
+ </div>
24
+ </div>
25
+ <% end %>
26
+
27
+ <% if Lato.config.webauthn_connection %>
28
+ <div class="list-group-item p-3">
29
+ <div class="row align-items-center">
30
+ <div class="col">
31
+ <span class="fw-bold"><%= I18n.t('lato_users.webauthn_connection') %></span>
32
+ </div>
33
+ <div class="col-auto">
34
+ <span class="badge bg-primary">
35
+ <%= I18n.t('lato_users.devices_count') %>: <%= @user.webauthn_public_key.present? ? 1 : 0 %>
36
+ </span>
37
+ </div>
38
+ </div>
39
+ </div>
40
+ <% end %>
41
+ </div>
42
+ </div>
43
+ </div>
44
+ <% end %>
45
+
46
+ <div class="row mb-4">
47
+ <div class="col-12">
48
+ <h5 class="fs-6 fw-bold mb-3"><%= I18n.t('lato_users.signup_logs') %></h5>
49
+ <% if @user.lato_log_user_signups.any? %>
50
+ <div class="list-group">
51
+ <% @user.lato_log_user_signups.order(created_at: :desc).limit(5).each do |log| %>
52
+ <div class="list-group-item p-3">
53
+ <div class="row align-items-center gy-2">
54
+ <div class="col-12 col-md-3 text-muted small">
55
+ <i class="bi bi-calendar-event me-2"></i>
56
+ <%= l(log.created_at, format: :long) %>
57
+ </div>
58
+ <div class="col-12 col-md-3">
59
+ <div class="d-flex align-items-center">
60
+ <i class="bi bi-globe me-2 text-muted d-md-none"></i>
61
+ <code class="text-dark"><%= log.ip_address %></code>
62
+ </div>
63
+ </div>
64
+ <div class="col-12 col-md-6 text-muted small">
65
+ <div class="d-flex align-items-center">
66
+ <i class="bi bi-laptop me-2 d-md-none"></i>
67
+ <span class="text-truncate d-block" style="max-width: 100%;" title="<%= log.user_agent %>" data-bs-toggle="tooltip">
68
+ <%= log.user_agent %>
69
+ </span>
70
+ </div>
71
+ </div>
72
+ </div>
73
+ </div>
74
+ <% end %>
75
+ </div>
76
+ <% else %>
77
+ <div class="alert alert-light border text-center text-muted">
78
+ <i class="bi bi-info-circle me-2"></i><%= I18n.t('lato_users.no_logs_found') %>
79
+ </div>
80
+ <% end %>
81
+ </div>
82
+ </div>
83
+
84
+ <div class="row mb-4">
85
+ <div class="col-12">
86
+ <h5 class="fs-6 fw-bold mb-3"><%= I18n.t('lato_users.login_logs') %></h5>
87
+ <% if @user.lato_log_user_signins.any? %>
88
+ <div class="list-group">
89
+ <% @user.lato_log_user_signins.order(created_at: :desc).limit(5).each do |log| %>
90
+ <div class="list-group-item p-3">
91
+ <div class="row align-items-center gy-2">
92
+ <div class="col-12 col-md-3 text-muted small">
93
+ <i class="bi bi-calendar-event me-2"></i>
94
+ <%= l(log.created_at, format: :long) %>
95
+ </div>
96
+ <div class="col-12 col-md-3">
97
+ <div class="d-flex align-items-center">
98
+ <i class="bi bi-globe me-2 text-muted d-md-none"></i>
99
+ <code class="text-dark"><%= log.ip_address %></code>
100
+ </div>
101
+ </div>
102
+ <div class="col-12 col-md-6 text-muted small">
103
+ <div class="d-flex align-items-center">
104
+ <i class="bi bi-laptop me-2 d-md-none"></i>
105
+ <span class="text-truncate d-block" style="max-width: 100%;" title="<%= log.user_agent %>" data-bs-toggle="tooltip">
106
+ <%= log.user_agent %>
107
+ </span>
108
+ </div>
109
+ </div>
110
+ </div>
111
+ </div>
112
+ <% end %>
113
+ </div>
114
+ <% else %>
115
+ <div class="alert alert-light border text-center text-muted">
116
+ <i class="bi bi-info-circle me-2"></i><%= I18n.t('lato_users.no_logs_found') %>
117
+ </div>
118
+ <% end %>
119
+ </div>
120
+ </div>
121
+
122
+ <div class="row">
123
+ <div class="col-12">
124
+ <h5 class="fs-6 fw-bold mb-3"><%= I18n.t('lato_users.password_management') %></h5>
125
+
126
+ <% if @user.password_digest.present? %>
127
+ <div class="d-flex flex-column flex-md-row align-items-md-center justify-content-between p-3 bg-success bg-opacity-10 border border-success rounded">
128
+ <div class="d-flex align-items-center mb-3 mb-md-0">
129
+ <div class="me-3 text-success">
130
+ <i class="bi bi-shield-lock-fill fs-4"></i>
131
+ </div>
132
+ <div>
133
+ <h5 class="fs-6 fw-bold text-success mb-0"><%= I18n.t('lato_users.password_set') %></h5>
134
+ </div>
135
+ </div>
136
+ <div class="d-flex gap-2">
137
+ <%= button_to force_update_password_user_path(@user), method: :patch, class: "btn btn-danger btn-sm d-flex align-items-center", form: { data: { turbo_confirm: I18n.t('lato_users.are_you_sure') } } do %>
138
+ <%= I18n.t('lato_users.force_password_update') %>
139
+ <% end %>
140
+ </div>
141
+ </div>
142
+ <% else %>
143
+ <div class="d-flex flex-column flex-md-row align-items-md-center justify-content-between p-3 bg-danger bg-opacity-10 border border-danger rounded">
144
+ <div class="d-flex align-items-center mb-3 mb-md-0">
145
+ <div class="me-3 text-danger">
146
+ <i class="bi bi-shield-exclamation fs-4"></i>
147
+ </div>
148
+ <div>
149
+ <h5 class="fs-6 fw-bold text-danger mb-0"><%= I18n.t('lato_users.password_not_set') %></h5>
150
+ </div>
151
+ </div>
152
+ <div class="d-flex gap-2">
153
+ <%= button_to force_update_password_user_path(@user), method: :patch, class: "btn btn-danger btn-sm d-flex align-items-center", form: { data: { turbo_confirm: I18n.t('lato_users.are_you_sure') } } do %>
154
+ <%= I18n.t('lato_users.force_password_update') %>
155
+ <% end %>
156
+ </div>
157
+ </div>
158
+ <% end %>
159
+ </div>
160
+ </div>
161
+ </div>
162
+ </div>
@@ -0,0 +1,98 @@
1
+ <%
2
+
3
+ user ||= Lato::User.new
4
+
5
+ %>
6
+
7
+ <%= turbo_frame_tag dom_id(user, 'form') do %>
8
+ <%= form_with model: user, url: user.persisted? ? lato_users.user_path(user) : lato_users.users_path, data: { turbo_frame: '_self', controller: 'lato-form lato-users-form' } do |form| %>
9
+ <%= lato_form_notices class: %w[mb-3] %>
10
+ <%= lato_form_errors user, class: %w[mb-3] %>
11
+
12
+ <% unless params[:redirect_to].blank? %>
13
+ <%= hidden_field_tag :redirect_to, params[:redirect_to] %>
14
+ <% end %>
15
+
16
+ <div class="row">
17
+ <div class="col col-12 mb-3">
18
+ <%= lato_form_item_label form, :email %>
19
+ <%= lato_form_item_input_email form, :email, required: true %>
20
+ </div>
21
+
22
+ <div class="col col-12 col-md-6 mb-3">
23
+ <%= lato_form_item_label form, :first_name %>
24
+ <%= lato_form_item_input_text form, :first_name, required: true %>
25
+ </div>
26
+
27
+ <div class="col col-12 col-md-6 mb-3">
28
+ <%= lato_form_item_label form, :last_name %>
29
+ <%= lato_form_item_input_text form, :last_name, required: true %>
30
+ </div>
31
+ </div>
32
+
33
+ <% unless user.persisted?%>
34
+ <div class="row">
35
+ <div class="col col-12 col-md-6 mb-3">
36
+ <%= lato_form_item_label form, :password %>
37
+ <%= lato_form_item_input_password form, :password, required: true, data: { lato_users_form_target: 'password' } %>
38
+ </div>
39
+
40
+ <div class="col col-12 col-md-6 mb-3">
41
+ <%= lato_form_item_label form, :password_confirmation %>
42
+ <%= lato_form_item_input_password form, :password_confirmation, required: true, data: { lato_users_form_target: 'passwordConfirmation' } %>
43
+ </div>
44
+ </div>
45
+
46
+ <div class="row mb-3">
47
+ <div class="col-12">
48
+ <div class="card bg-light">
49
+ <div class="card-body">
50
+ <div class="row align-items-center">
51
+ <div class="col-auto">
52
+ <button class="btn btn-primary" data-action="click->lato-users-form#generatePassword">
53
+ <i class="bi bi-shuffle me-2"></i>
54
+ <%= I18n.t('lato_users.generate_password') %>
55
+ </button>
56
+ </div>
57
+ <div class="col">
58
+ <div class="input-group">
59
+ <span class="input-group-text"><i class="bi bi-key"></i></span>
60
+ <input type="text" class="form-control font-monospace" readonly data-lato-users-form-target="generatedPassword" placeholder="...">
61
+ </div>
62
+ </div>
63
+ </div>
64
+ </div>
65
+ </div>
66
+ </div>
67
+ </div>
68
+ <% end %>
69
+
70
+ <% if @available_admin_permissions.any? %>
71
+ <div class="accordion mb-3" id="permissionsAccordion">
72
+ <div class="accordion-item">
73
+ <h2 class="accordion-header" id="headingPermissions">
74
+ <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapsePermissions" aria-expanded="false" aria-controls="collapsePermissions">
75
+ <%= I18n.t('lato_users.permissions') %>
76
+ </button>
77
+ </h2>
78
+ <div id="collapsePermissions" class="accordion-collapse collapse" aria-labelledby="headingPermissions" data-bs-parent="#permissionsAccordion">
79
+ <div class="accordion-body">
80
+ <div class="row">
81
+ <% @available_admin_permissions.each_with_index do |permission, index| %>
82
+ <div class="col-12 col-md-6 <%= 'mb-3' unless index == @available_admin_permissions.size - 1 %>">
83
+ <%= lato_form_item_input_check form, permission.to_sym, permission.humanize %>
84
+ </div>
85
+ <% end %>
86
+ </div>
87
+ </div>
88
+ </div>
89
+ </div>
90
+ </div>
91
+ <% end %>
92
+
93
+ <div class="d-flex justify-content-end">
94
+ <%= lato_form_submit form, user.persisted? ? I18n.t('lato_users.cta_update') : I18n.t('lato_users.cta_confirm'), class: %w[btn-success] %>
95
+ </div>
96
+
97
+ <% end %>
98
+ <% end %>
@@ -0,0 +1,13 @@
1
+ <%= lato_page_head I18n.t('lato_users.edit_user'), [
2
+ { label: I18n.t('lato_users.users'), path: lato_users.users_path },
3
+ { label: I18n.t('lato_users.edit_user') }
4
+ ] %>
5
+
6
+ <div class="card mb-4">
7
+ <div class="card-header">
8
+ <h2 class="fs-4 mb-0"><%= I18n.t('lato_users.edit_user') %></h2>
9
+ </div>
10
+ <div class="card-body">
11
+ <%= render 'lato_users/users/form', user: @user %>
12
+ </div>
13
+ </div>
@@ -0,0 +1,24 @@
1
+ <%= lato_page_head I18n.t('lato_users.users') %>
2
+
3
+ <div class="card mb-4">
4
+ <div class="card-body">
5
+ <%= lato_index @users,
6
+ custom_actions: {
7
+ invitations: {
8
+ path: lato_users.invitations_path,
9
+ icon: 'bi bi-envelope',
10
+ aria_label: I18n.t('lato_users.invitations'),
11
+ title: I18n.t('lato_users.invitations'),
12
+ data: { controller: 'lato-tooltip' },
13
+ },
14
+ create: {
15
+ path: lato_users.new_user_path,
16
+ icon: 'bi bi-plus',
17
+ aria_label: I18n.t('lato_users.add_user'),
18
+ title: I18n.t('lato_users.add_user'),
19
+ data: { lato_action_target: 'trigger', turbo_frame: dom_id(Lato::User.new, 'form'), action_title: I18n.t('lato_users.add_user'), controller: 'lato-tooltip' },
20
+ }
21
+ }
22
+ %>
23
+ </div>
24
+ </div>
@@ -0,0 +1,13 @@
1
+ <%= lato_page_head I18n.t('lato_users.add_user'), [
2
+ { label: I18n.t('lato_users.users'), path: lato_users.users_path },
3
+ { label: I18n.t('lato_users.add_user') }
4
+ ] %>
5
+
6
+ <div class="card mb-4">
7
+ <div class="card-header">
8
+ <h2 class="fs-4 mb-0"><%= I18n.t('lato_users.add_user') %></h2>
9
+ </div>
10
+ <div class="card-body">
11
+ <%= render 'lato_users/users/form', user: @user %>
12
+ </div>
13
+ </div>
@@ -0,0 +1,12 @@
1
+ <%= lato_page_head I18n.t('lato_users.show_user'), [
2
+ { label: I18n.t('lato_users.users'), path: lato_users.users_path },
3
+ { label: I18n.t('lato_users.show_user') }
4
+ ] %>
5
+
6
+ <%= render 'card_account_informations' %>
7
+
8
+ <%= render 'card_email_verification' %>
9
+
10
+ <%= render 'card_security' %>
11
+
12
+ <%= render 'card_privacy_and_terms' %>
@@ -0,0 +1,2 @@
1
+ pin "lato_users/application", to: "lato_users/application.js"
2
+ pin_all_from LatoUsers::Engine.root.join("app/assets/javascripts/lato_users/controllers"), under: "controllers", to: "lato_users/controllers"
@@ -0,0 +1,61 @@
1
+ en:
2
+ lato_users:
3
+ users: Users
4
+ add_user: Add User
5
+ edit_user: Edit User
6
+ show_user: User details
7
+ cta_show: Show
8
+ cta_edit: Edit
9
+ cta_update: Update
10
+ cta_delete: Delete
11
+ cta_confirm: Confirm
12
+ cta_delete_confirm: Are you sure you want to delete this user?
13
+ cannot_delete_self: You cannot delete your own user account.
14
+ account_informations: Account Informations
15
+ email_verification: "Email Verification"
16
+ email_verified: "Email Verified"
17
+ email_verified_at: "Verified on %{date}"
18
+ email_verification_required: "Verification required"
19
+ revoke_verification: "Revoke verification"
20
+ are_you_sure: "Are you sure?"
21
+ email_not_verified: "Email not verified"
22
+ resend_verification_email: "Resend verification email"
23
+ manual_verify_email: "Manually verify"
24
+ verification_email_sent: "Verification email sent"
25
+ email_manually_verified: "Email manually verified"
26
+ email_verification_revoked: "Email verification revoked"
27
+ privacy_and_terms: "Privacy Policy and Terms"
28
+ privacy_policy: "Privacy Policy"
29
+ terms_and_conditions: "Terms and Conditions"
30
+ accepted_version: "Accepted version"
31
+ current_version: "Current version"
32
+ version_outdated: "Version outdated"
33
+ version_up_to_date: "Version up to date"
34
+ security: "Security"
35
+ signup_logs: "Signup Logs"
36
+ date: "Date"
37
+ ip_address: "IP Address"
38
+ user_agent: "User Agent"
39
+ no_logs_found: "No logs found"
40
+ login_logs: "Login Logs"
41
+ password_management: "Password Management"
42
+ password_set: "Password set"
43
+ password_not_set: "Password not set"
44
+ send_reset_password_email: "Send reset password email"
45
+ force_password_update: "Force password update"
46
+ recover_password_email_sent: "Recovery password email sent"
47
+ recover_password_email_failed: "Failed to send recovery password email"
48
+ password_updated: "Password updated: %{password}"
49
+ password_update_failed: "Failed to update password"
50
+ authenticator_connection: "Authenticator Connection"
51
+ invitations: "Invitations"
52
+ add_invitation: "Add Invitation"
53
+ invitation_created: "Invitation created"
54
+ invitation_destroyed: "Invitation destroyed"
55
+ webauthn_connection: "WebAuthn Connection"
56
+ connected: "Connected"
57
+ not_connected: "Not Connected"
58
+ devices_count: "Connected Devices"
59
+ invitations: "Invitations"
60
+ generate_password: 'Generate Password'
61
+ permissions: "Permissions"
@@ -0,0 +1,60 @@
1
+ it:
2
+ lato_users:
3
+ users: Utenti
4
+ add_user: Aggiungi Utente
5
+ edit_user: Modifica Utente
6
+ show_user: Dettagli Utente
7
+ cta_show: Visualizza
8
+ cta_edit: Modifica
9
+ cta_update: Aggiorna
10
+ cta_delete: Elimina
11
+ cta_confirm: Conferma
12
+ cta_delete_confirm: Sei sicuro di voler eliminare questo utente?
13
+ cannot_delete_self: Non puoi eliminare il tuo account utente.
14
+ account_informations: Informazioni Account
15
+ email_verification: "Verifica Email"
16
+ email_verified: "Email Verificata"
17
+ email_verified_at: "Verificata il %{date}"
18
+ email_verification_required: "Verifica richiesta"
19
+ revoke_verification: "Revoca verifica"
20
+ are_you_sure: "Sei sicuro?"
21
+ email_not_verified: "Email non verificata"
22
+ resend_verification_email: "Invia nuovamente email di verifica"
23
+ manual_verify_email: "Verifica manualmente"
24
+ verification_email_sent: "Email di verifica inviata"
25
+ email_manually_verified: "Email verificata manualmente"
26
+ email_verification_revoked: "Verifica email revocata"
27
+ privacy_and_terms: "Privacy Policy e Termini"
28
+ privacy_policy: "Privacy Policy"
29
+ terms_and_conditions: "Termini e Condizioni"
30
+ accepted_version: "Versione accettata"
31
+ current_version: "Versione corrente"
32
+ version_outdated: "Versione obsoleta"
33
+ version_up_to_date: "Versione aggiornata"
34
+ security: "Sicurezza"
35
+ signup_logs: "Log di Registrazione"
36
+ date: "Data"
37
+ ip_address: "Indirizzo IP"
38
+ user_agent: "User Agent"
39
+ no_logs_found: "Nessun log trovato"
40
+ login_logs: "Log di Accesso"
41
+ password_management: "Gestione Password"
42
+ password_set: "Password impostata"
43
+ password_not_set: "Password non impostata"
44
+ send_reset_password_email: "Invia email di reset password"
45
+ force_password_update: "Forza aggiornamento password"
46
+ recover_password_email_sent: "Email di recupero password inviata"
47
+ recover_password_email_failed: "Impossibile inviare email di recupero password"
48
+ password_updated: "Password aggiornata: %{password}"
49
+ password_update_failed: "Impossibile aggiornare la password"
50
+ authenticator_connection: "Connessione Authenticator"
51
+ invitations: "Inviti"
52
+ add_invitation: "Aggiungi Invito"
53
+ invitation_created: "Invito creato"
54
+ invitation_destroyed: "Invito eliminato"
55
+ webauthn_connection: "Connessione WebAuthn"
56
+ connected: "Connesso"
57
+ not_connected: "Non Connesso"
58
+ devices_count: "Dispositivi Connessi"
59
+ generate_password: 'Genera Password'
60
+ permissions: "Permessi"
data/config/routes.rb ADDED
@@ -0,0 +1,14 @@
1
+ LatoUsers::Engine.routes.draw do
2
+ root 'application#index'
3
+
4
+ resources :users do
5
+ member do
6
+ post :resend_verification_email
7
+ patch :manual_verify_email
8
+ patch :revoke_email_verification
9
+ patch :force_update_password
10
+ end
11
+ end
12
+
13
+ resources :invitations, except: [:show, :edit, :update]
14
+ end
@@ -0,0 +1,5 @@
1
+ class AddLatoUsersAdminToLatoUser < ActiveRecord::Migration[7.1]
2
+ def change
3
+ add_column :lato_users, :lato_users_admin, :boolean, default: false
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+ module LatoUsers
2
+ # Config
3
+ # This class contains the default configuration of the engine.
4
+ ##
5
+ class Config
6
+
7
+ def initialize
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,13 @@
1
+ module LatoUsers
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace LatoUsers
4
+
5
+ initializer 'lato_users.importmap', before: 'importmap' do |app|
6
+ app.config.importmap.paths << root.join('config/importmap.rb')
7
+ end
8
+
9
+ initializer "lato_users.precompile" do |app|
10
+ app.config.assets.precompile << "lato_users_manifest.js"
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ module LatoUsers
2
+ VERSION = "3.0.0"
3
+ end
data/lib/lato_users.rb ADDED
@@ -0,0 +1,15 @@
1
+ require "lato_users/version"
2
+ require "lato_users/engine"
3
+ require "lato_users/config"
4
+
5
+ module LatoUsers
6
+ class << self
7
+ def config
8
+ @config ||= Config.new
9
+ end
10
+
11
+ def configure
12
+ yield config
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ require 'fileutils'
2
+
3
+ namespace :lato_users do
4
+ namespace :install do
5
+ desc 'Install Lato users engine and run tasks on main rails application'
6
+ # Usage: rails lato_users:install:application
7
+ task :application do
8
+ # Free for future use...
9
+ end
10
+ end
11
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lato_users
3
+ version: !ruby/object:Gem::Version
4
+ version: 3.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Gregorio Galante
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-12-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 7.1.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 7.1.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: lato
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: A Rails engine to manage application users on Lato projects!
42
+ email:
43
+ - me@gregoriogalante.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - MIT-LICENSE
49
+ - README.md
50
+ - Rakefile
51
+ - app/assets/config/lato_users_manifest.js
52
+ - app/assets/javascripts/lato_users/application.js
53
+ - app/assets/javascripts/lato_users/controllers/lato_users_form_controller.js
54
+ - app/assets/stylesheets/lato_users/application.scss
55
+ - app/controllers/lato_users/application_controller.rb
56
+ - app/controllers/lato_users/invitations_controller.rb
57
+ - app/controllers/lato_users/users_controller.rb
58
+ - app/helpers/lato_users/application_helper.rb
59
+ - app/helpers/lato_users/invitations_helper.rb
60
+ - app/helpers/lato_users/users_helper.rb
61
+ - app/jobs/lato_users/application_job.rb
62
+ - app/mailers/lato_users/application_mailer.rb
63
+ - app/views/lato_users/invitations/_form.html.erb
64
+ - app/views/lato_users/invitations/index.html.erb
65
+ - app/views/lato_users/invitations/new.html.erb
66
+ - app/views/lato_users/users/_card_account_informations.html.erb
67
+ - app/views/lato_users/users/_card_email_verification.html.erb
68
+ - app/views/lato_users/users/_card_privacy_and_terms.html.erb
69
+ - app/views/lato_users/users/_card_security.html.erb
70
+ - app/views/lato_users/users/_form.html.erb
71
+ - app/views/lato_users/users/edit.html.erb
72
+ - app/views/lato_users/users/index.html.erb
73
+ - app/views/lato_users/users/new.html.erb
74
+ - app/views/lato_users/users/show.html.erb
75
+ - config/importmap.rb
76
+ - config/locales/en.yml
77
+ - config/locales/it.yml
78
+ - config/routes.rb
79
+ - db/migrate/20250328072343_add_lato_users_admin_to_lato_user.rb
80
+ - lib/lato_users.rb
81
+ - lib/lato_users/config.rb
82
+ - lib/lato_users/engine.rb
83
+ - lib/lato_users/version.rb
84
+ - lib/tasks/lato_users_tasks.rake
85
+ homepage: https://github.com/Lato-org/lato_users
86
+ licenses:
87
+ - MIT
88
+ metadata:
89
+ homepage_uri: https://github.com/Lato-org/lato_users
90
+ source_code_uri: https://github.com/Lato-org/lato_users
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubygems_version: 3.4.1
107
+ signing_key:
108
+ specification_version: 4
109
+ summary: Another engine for Lato projects
110
+ test_files: []