lesli 5.1.12 → 5.1.13

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +3 -0
  3. data/app/assets/stylesheets/lesli/application.css +43 -3
  4. data/app/controllers/lesli/application_devise_controller.rb +9 -5
  5. data/app/controllers/lesli/application_lesli_controller.rb +4 -0
  6. data/app/controllers/lesli/items/attachments_controller.rb +200 -0
  7. data/app/controllers/lesli/items/discussions_controller.rb +94 -67
  8. data/app/controllers/lesli/items/tasks_controller.rb +132 -62
  9. data/app/helpers/lesli/turbo_helper.rb +0 -18
  10. data/app/interfaces/lesli/responder_interface.rb +58 -38
  11. data/app/mailers/lesli/application_lesli_mailer.rb +34 -8
  12. data/app/models/concerns/lesli/account_initializer.rb +11 -3
  13. data/app/models/concerns/lesli/account_logs.rb +5 -16
  14. data/app/models/concerns/lesli/items/activities.rb +29 -33
  15. data/app/models/concerns/lesli/items/attachments.rb +88 -0
  16. data/app/models/concerns/lesli/items/discussions.rb +25 -11
  17. data/app/models/concerns/lesli/items/ownership.rb +27 -0
  18. data/app/models/concerns/lesli/items/tasks.rb +26 -6
  19. data/app/models/concerns/lesli/user_extensions.rb +5 -9
  20. data/app/models/lesli/account.rb +10 -14
  21. data/app/models/lesli/items/activity.rb +25 -2
  22. data/app/models/lesli/items/attachment.rb +117 -0
  23. data/app/models/lesli/items/discussion.rb +6 -2
  24. data/app/models/lesli/items/task.rb +3 -1
  25. data/app/models/lesli/user.rb +12 -21
  26. data/app/views/lesli/abouts/welcome.html.erb +2 -5
  27. data/app/views/lesli/errors/not_found.html.erb +7 -21
  28. data/app/views/lesli/errors/unauthorized.html.erb +11 -27
  29. data/app/views/lesli/layouts/application-devise.html.erb +10 -9
  30. data/app/views/lesli/layouts/application-lesli.html.erb +1 -1
  31. data/app/views/lesli/partials/_application-lesli-assets.html.erb +3 -3
  32. data/app/views/lesli/partials/_application-lesli-header.html.erb +5 -9
  33. data/config/initializers/lesli.rb +4 -1
  34. data/config/locales/translations.en.yml +34 -0
  35. data/config/locales/translations.es.yml +34 -0
  36. data/db/seed/accounts.rb +5 -5
  37. data/db/seed/users.rb +8 -16
  38. data/db/seed/xyz.rb +38 -17
  39. data/lib/lesli/engine.rb +0 -4
  40. data/lib/lesli/router.rb +5 -2
  41. data/lib/lesli/version.rb +2 -2
  42. data/lib/migrate/items/activity_structure.rb +8 -8
  43. data/lib/migrate/items/attachment_structure.rb +9 -14
  44. data/lib/migrate/items/discussion_structure.rb +5 -5
  45. data/lib/migrate/items/task_structure.rb +2 -3
  46. data/lib/migrate/shared/account_structure.rb +1 -1
  47. data/lib/tasks/lesli_tasks.rake +2 -3
  48. data/readme.md +3 -3
  49. data/test/fixtures/lesli_accounts.yml +18 -0
  50. data/test/fixtures/lesli_users.yml +23 -0
  51. metadata +17 -25
  52. data/app/assets/stylesheets/lesli/application.tailwind.css +0 -33
  53. data/config/brakeman.yml +0 -44
  54. data/config/initializers/devise.rb +0 -27
@@ -0,0 +1,88 @@
1
+ =begin
2
+
3
+ Lesli
4
+
5
+ Copyright (c) 2026, Lesli Technologies, S. A.
6
+
7
+ This program is free software: you can redistribute it and/or modify
8
+ it under the terms of the GNU General Public License as published by
9
+ the Free Software Foundation, either version 3 of the License, or
10
+ (at your option) any later version.
11
+
12
+ This program is distributed in the hope that it will be useful,
13
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ GNU General Public License for more details.
16
+
17
+ You should have received a copy of the GNU General Public License
18
+ along with this program. If not, see http://www.gnu.org/licenses/.
19
+
20
+ Lesli · Ruby on Rails SaaS Development Framework.
21
+
22
+ Made with ♥ by LesliTech
23
+ Building a better future, one line of code at a time.
24
+
25
+ @contact hello@lesli.tech
26
+ @website https://www.lesli.tech
27
+ @license GPLv3 http://www.gnu.org/licenses/gpl-3.0.en.html
28
+
29
+ // · ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~
30
+ // ·
31
+ =end
32
+
33
+ module Lesli
34
+ module Items
35
+ module Attachments
36
+ extend ActiveSupport::Concern
37
+ include Lesli::Items::Ownership
38
+
39
+ included do
40
+ class_attribute :lesli_attachments_class, instance_accessor: false
41
+ lesli_attachments_setup
42
+ end
43
+
44
+ class_methods do
45
+ def is_lesli_attachable?
46
+ true
47
+ end
48
+
49
+ def lesli_attachments_setup(
50
+ use: nil,
51
+ as: :attachable,
52
+ association_name: :attachments
53
+ )
54
+ klass = if use
55
+ use.to_s.constantize
56
+ else
57
+ "#{name.deconstantize}::Items::Attachment".constantize
58
+ end
59
+
60
+ self.lesli_attachments_class = klass
61
+
62
+ expected_table = lesli_expected_attachments_table_name_for(klass)
63
+ klass.table_name = expected_table if klass.table_name != expected_table
64
+
65
+ has_many(
66
+ association_name,
67
+ -> { order(created_at: :desc) },
68
+ as: as,
69
+ class_name: klass.name,
70
+ dependent: :destroy
71
+ )
72
+ rescue NameError
73
+ raise NameError,
74
+ "Attachment class not found for #{name}. " \
75
+ "Expected #{name.deconstantize}::Items::Attachment or pass `use:`"
76
+ end
77
+
78
+ private
79
+
80
+ def lesli_expected_attachments_table_name_for(klass)
81
+ parts = klass.name.split("::").map(&:underscore)
82
+ parts.join("_").pluralize
83
+ end
84
+ end
85
+
86
+ end
87
+ end
88
+ end
@@ -34,9 +34,13 @@ module Lesli
34
34
  module Items
35
35
  module Discussions
36
36
  extend ActiveSupport::Concern
37
+ include Lesli::Items::Ownership
37
38
 
38
39
  included do
39
40
  class_attribute :lesli_discussions_class, instance_accessor: false
41
+ class_attribute :lesli_discussions_association_name,
42
+ instance_accessor: false,
43
+ default: :discussions
40
44
  lesli_discussions_setup
41
45
  end
42
46
 
@@ -47,8 +51,7 @@ module Lesli
47
51
  true
48
52
  end
49
53
 
50
- # Automatically sets the associations to the
51
- # engine model that is implementing the task items
54
+ # Resolve the concrete discussion model for the engine.
52
55
  def lesli_discussions_setup(use: nil, as: :discussable, association_name: :discussions)
53
56
  klass = if use
54
57
  use.to_s.constantize
@@ -57,6 +60,7 @@ module Lesli
57
60
  end
58
61
 
59
62
  self.lesli_discussions_class = klass
63
+ self.lesli_discussions_association_name = association_name.to_sym
60
64
 
61
65
  # ✅ Auto-fix table name (works even when engine defines table_name_prefix)
62
66
  expected_table = lesli_expected_discussions_table_name_for(klass)
@@ -65,13 +69,8 @@ module Lesli
65
69
  klass.table_name = expected_table
66
70
  end
67
71
 
68
- # Set associations
69
- # Example:
70
- # class LesliSupport::Ticket
71
- # has_many :lesli_support_tasks
72
- # end
73
72
  has_many(association_name,
74
- -> { where(deleted_at: nil).order(created_at: :desc) },
73
+ -> { order(created_at: :desc, id: :desc) },
75
74
  as: as,
76
75
  class_name: klass.name,
77
76
  dependent: :destroy)
@@ -79,14 +78,29 @@ module Lesli
79
78
 
80
79
  private
81
80
 
82
- # Build: "lesli_support_item_activities" from "LesliSupport::Item::Activity"
81
+ # Build: "lesli_support_items_discussions" from
82
+ # "LesliSupport::Items::Discussion"
83
83
  #
84
84
  # We DO NOT rely on ActiveRecord's table_name_prefix, because that’s what breaks your case.
85
85
  def lesli_expected_discussions_table_name_for(klass)
86
- parts = klass.name.split("::").map(&:underscore) # ["lesli_support", "item", "discussion"]
87
- "#{parts.join('_').pluralize}" # "lesli_support_item_discussions"
86
+ parts = klass.name.split("::").map(&:underscore)
87
+ "#{parts.join('_').pluralize}"
88
88
  end
89
89
  end
90
+
91
+ def add_comment!(author:, message:)
92
+ discussion_association.create!(
93
+ author: author,
94
+ account: lesli_item_account,
95
+ message: message
96
+ )
97
+ end
98
+
99
+ private
100
+
101
+ def discussion_association
102
+ public_send(self.class.lesli_discussions_association_name)
103
+ end
90
104
  end
91
105
  end
92
106
  end
@@ -0,0 +1,27 @@
1
+ module Lesli
2
+ module Items
3
+ module Ownership
4
+ extend ActiveSupport::Concern
5
+
6
+ # Item records use the same account as their parent resource. For
7
+ # engine resources this is an Engine::Account; Core resources may
8
+ # return Lesli::Account directly.
9
+ def lesli_item_account
10
+ account
11
+ end
12
+
13
+ def lesli_item_accessible_by?(user)
14
+ return false if user.blank?
15
+
16
+ item_account = lesli_item_account
17
+ core_account = if item_account.respond_to?(:account)
18
+ item_account.account
19
+ else
20
+ item_account
21
+ end
22
+
23
+ core_account == user.account
24
+ end
25
+ end
26
+ end
27
+ end
@@ -34,9 +34,13 @@ module Lesli
34
34
  module Items
35
35
  module Tasks
36
36
  extend ActiveSupport::Concern
37
+ include Lesli::Items::Ownership
37
38
 
38
39
  included do
39
40
  class_attribute :lesli_tasks_class, instance_accessor: false
41
+ class_attribute :lesli_tasks_association_name,
42
+ instance_accessor: false,
43
+ default: :tasks
40
44
  lesli_tasks_setup
41
45
  end
42
46
 
@@ -57,6 +61,7 @@ module Lesli
57
61
  end
58
62
 
59
63
  self.lesli_tasks_class = klass
64
+ self.lesli_tasks_association_name = association_name.to_sym
60
65
 
61
66
  # ✅ Auto-fix table name (works even when engine defines table_name_prefix)
62
67
  expected_table = lesli_expected_task_table_name_for(klass)
@@ -71,25 +76,40 @@ module Lesli
71
76
  # has_many :lesli_support_tasks
72
77
  # end
73
78
  has_many(association_name,
74
- -> { where(deleted_at: nil).order(created_at: :desc) },
79
+ -> { order(created_at: :desc) },
75
80
  as: as,
76
81
  class_name: klass.name,
77
82
  dependent: :destroy)
78
83
  rescue NameError
79
84
  raise NameError,
80
- "Task class not found for #{name}. Expected #{name.deconstantize}::Task or pass `use:`"
85
+ "Task class not found for #{name}. Expected #{name.deconstantize}::Items::Task or pass `use:`"
81
86
  end
82
87
 
83
88
  private
84
89
 
85
- # Build: "lesli_support_item_activities" from "LesliSupport::Item::Activity"
90
+ # Build "lesli_support_items_tasks" from
91
+ # "LesliSupport::Items::Task".
86
92
  #
87
- # We DO NOT rely on ActiveRecord's table_name_prefix, because that’s what breaks your case.
93
+ # Do not rely on Active Record's table_name_prefix here.
88
94
  def lesli_expected_task_table_name_for(klass)
89
- parts = klass.name.split("::").map(&:underscore) # ["lesli_support", "item", "task"]
90
- "#{parts.join('_').pluralize}" # "lesli_support_item_tasks"
95
+ parts = klass.name.split("::").map(&:underscore)
96
+ parts.join("_").pluralize
91
97
  end
92
98
  end
99
+
100
+ def add_task!(creator:, title:)
101
+ task_association.create!(
102
+ user: creator,
103
+ account: lesli_item_account,
104
+ title: title
105
+ )
106
+ end
107
+
108
+ private
109
+
110
+ def task_association
111
+ public_send(self.class.lesli_tasks_association_name)
112
+ end
93
113
  end
94
114
  end
95
115
  end
@@ -94,22 +94,18 @@ module Lesli
94
94
  subject:nil # resource related to the log
95
95
  )
96
96
 
97
- return unless defined?(LesliAudit) && respond_to?(:logs)
97
+ return unless defined?(LesliAudit::ActivityAudit)
98
98
 
99
- self.logs.create!({
99
+ LesliAudit::ActivityAudit.record_user!(
100
+ user: self,
100
101
  engine: engine,
101
102
  source: source,
102
103
  action: action,
103
-
104
104
  operation: operation,
105
105
  description: description,
106
106
  session_id: session_id,
107
-
108
- subject_type: subject&.class&.name,
109
- subject_id: subject&.id,
110
-
111
- account: self&.account&.audit
112
- })
107
+ subject: subject
108
+ )
113
109
  end
114
110
  end
115
111
  end
@@ -45,26 +45,21 @@ module Lesli
45
45
  has_many :settings
46
46
  has_many :locations
47
47
  has_many :currencies
48
- has_many :requests, class_name: "LesliAudit::AccountRequest"
49
- has_many :logs, class_name: "LesliAudit::AccountLog"
50
48
 
51
49
 
52
50
  # third-party engines associations
53
- has_one :bell, class_name: "LesliBell::Account"
54
- has_one :help, class_name: "LesliHelp::Account"
55
- has_one :audit, class_name: "LesliAudit::Account"
56
- has_one :admin, class_name: "LesliAdmin::Account"
57
- has_one :guard, class_name: "LesliGuard::Account"
58
- has_one :shield, class_name: "LesliShield::Account"
51
+ has_one :bell, class_name: "LesliBell::Account", foreign_key: :account_id, inverse_of: :account
52
+ has_one :admin, class_name: "LesliAdmin::Account", foreign_key: :account_id, inverse_of: :account
53
+ has_one :audit, class_name: "LesliAudit::Account", foreign_key: :account_id, inverse_of: :account
54
+ has_one :shield, class_name: "LesliShield::Account", foreign_key: :account_id, inverse_of: :account
59
55
  has_one :letter, class_name: "LesliLetter::Account"
60
56
  has_one :papers, class_name: "LesliPapers::Account"
61
- has_one :support, class_name: "LesliSupport::Account"
62
- has_one :contacts, class_name: "LesliContacts::Account"
63
- has_one :calendar, class_name: "LesliCalendar::Account"
64
- has_one :dashboard, class_name: "LesliDashboard::Account"
65
-
57
+ has_one :mailer, class_name: "LesliMailer::Account", foreign_key: :account_id, inverse_of: :account
58
+ has_one :support, class_name: "LesliSupport::Account", foreign_key: :account_id, inverse_of: :account
59
+ has_one :contacts, class_name: "LesliContacts::Account", foreign_key: :account_id, inverse_of: :account
60
+ has_one :calendar, class_name: "LesliCalendar::Account", foreign_key: :account_id, inverse_of: :account
61
+ has_one :dashboard, class_name: "LesliDashboard::Account", foreign_key: :account_id, inverse_of: :account
66
62
 
67
- validates :status, presence: true
68
63
 
69
64
  # account statuses
70
65
  enum :status, {
@@ -83,6 +78,7 @@ module Lesli
83
78
 
84
79
  # required a name for the lesli account
85
80
  validates :name, :presence => true
81
+ validates :status, presence: true
86
82
 
87
83
 
88
84
  # initializers for new accounts
@@ -34,9 +34,32 @@ module Lesli
34
34
  module Items
35
35
  class Activity < ApplicationRecord
36
36
  self.abstract_class = true
37
- belongs_to :user, class_name: "Lesli::User"
38
- belongs_to :account, class_name: "Lesli::Account"
37
+
38
+ # Activity ownership contract:
39
+ # - subject is the resource affected by the event.
40
+ # - actor is the Lesli user who performed the event. The existing
41
+ # user_id column is kept as the foreign key.
42
+ # - account is declared by the concrete engine model.
43
+ # - activity_code is a semantic event name, not a column name.
44
+ belongs_to :actor, class_name: "Lesli::User", foreign_key: :user_id
39
45
  belongs_to :subject, polymorphic: true
46
+
47
+ validate :metadata_is_an_object
48
+ validates :activity_type, presence: true
49
+ validates :activity_code,
50
+ presence: true,
51
+ format: {
52
+ with: /\A[a-z][a-z0-9_]*\z/,
53
+ message: "must be a semantic snake_case event name"
54
+ }
55
+
56
+ private
57
+
58
+ def metadata_is_an_object
59
+ return if metadata.is_a?(Hash)
60
+
61
+ errors.add(:metadata, "must be a JSON object")
62
+ end
40
63
  end
41
64
  end
42
65
  end
@@ -0,0 +1,117 @@
1
+ =begin
2
+
3
+ Lesli
4
+
5
+ Copyright (c) 2026, Lesli Technologies, S. A.
6
+
7
+ This program is free software: you can redistribute it and/or modify
8
+ it under the terms of the GNU General Public License as published by
9
+ the Free Software Foundation, either version 3 of the License, or
10
+ (at your option) any later version.
11
+
12
+ This program is distributed in the hope that it will be useful,
13
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ GNU General Public License for more details.
16
+
17
+ You should have received a copy of the GNU General Public License
18
+ along with this program. If not, see http://www.gnu.org/licenses/.
19
+
20
+ Lesli · Ruby on Rails SaaS Development Framework.
21
+
22
+ Made with ♥ by LesliTech
23
+ Building a better future, one line of code at a time.
24
+
25
+ @contact hello@lesli.tech
26
+ @website https://www.lesli.tech
27
+ @license GPLv3 http://www.gnu.org/licenses/gpl-3.0.en.html
28
+
29
+ // · ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~
30
+ // ·
31
+ =end
32
+
33
+ module Lesli
34
+ module Items
35
+ class Attachment < ApplicationLesliRecord
36
+ DEFAULT_MAX_FILE_SIZE = 25.megabytes
37
+ DEFAULT_CONTENT_TYPES = %w[
38
+ application/pdf
39
+ application/msword
40
+ application/vnd.ms-excel
41
+ application/vnd.ms-powerpoint
42
+ application/vnd.openxmlformats-officedocument.presentationml.presentation
43
+ application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
44
+ application/vnd.openxmlformats-officedocument.wordprocessingml.document
45
+ application/zip
46
+ image/gif
47
+ image/jpeg
48
+ image/png
49
+ image/webp
50
+ text/csv
51
+ text/plain
52
+ ].freeze
53
+
54
+ self.abstract_class = true
55
+
56
+ class_attribute :lesli_attachment_max_file_size,
57
+ instance_accessor: false,
58
+ default: DEFAULT_MAX_FILE_SIZE
59
+ class_attribute :lesli_attachment_content_types,
60
+ instance_accessor: false,
61
+ default: DEFAULT_CONTENT_TYPES
62
+
63
+ belongs_to :user, class_name: "Lesli::User"
64
+ belongs_to :attachable, polymorphic: true
65
+
66
+ has_one_attached :file
67
+
68
+ validates :label, length: { maximum: 255 }, allow_blank: true
69
+ validates :category, length: { maximum: 100 }, allow_blank: true
70
+ validate :file_is_attached
71
+ validate :file_size_is_allowed
72
+ validate :file_type_is_allowed
73
+
74
+ def display_name
75
+ label.presence || file.filename.to_s
76
+ end
77
+
78
+ private
79
+
80
+ def file_is_attached
81
+ return if file.attached?
82
+
83
+ errors.add(
84
+ :file,
85
+ I18n.t("lesli.items.attachments.errors.required")
86
+ )
87
+ end
88
+
89
+ def file_size_is_allowed
90
+ return unless file.attached?
91
+ return if file.blob.byte_size <= self.class.lesli_attachment_max_file_size
92
+
93
+ errors.add(
94
+ :file,
95
+ I18n.t(
96
+ "lesli.items.attachments.errors.too_large",
97
+ size: ActiveSupport::NumberHelper.number_to_human_size(
98
+ self.class.lesli_attachment_max_file_size
99
+ )
100
+ )
101
+ )
102
+ end
103
+
104
+ def file_type_is_allowed
105
+ return unless file.attached?
106
+ return if self.class.lesli_attachment_content_types.include?(
107
+ file.blob.content_type
108
+ )
109
+
110
+ errors.add(
111
+ :file,
112
+ I18n.t("lesli.items.attachments.errors.unsupported_type")
113
+ )
114
+ end
115
+ end
116
+ end
117
+ end
@@ -2,9 +2,13 @@ module Lesli
2
2
  module Items
3
3
  class Discussion < ApplicationRecord
4
4
  self.abstract_class = true
5
- belongs_to :user, class_name: "Lesli::User"
6
- belongs_to :account, class_name: "Lesli::Account"
5
+
6
+ belongs_to :author,
7
+ class_name: "Lesli::User",
8
+ foreign_key: :user_id
7
9
  belongs_to :discussable, polymorphic: true
10
+
11
+ validates :message, presence: true
8
12
  end
9
13
  end
10
14
  end
@@ -34,9 +34,11 @@ module Lesli
34
34
  module Items
35
35
  class Task < ApplicationRecord
36
36
  self.abstract_class = true
37
+
37
38
  belongs_to :user, class_name: "Lesli::User"
38
- belongs_to :account, class_name: "Lesli::Account"
39
39
  belongs_to :taskable, polymorphic: true
40
+
41
+ validates :title, presence: true
40
42
  end
41
43
  end
42
44
  end
@@ -41,17 +41,6 @@ module Lesli
41
41
  uniqueness: true
42
42
  );
43
43
 
44
- # devise implementation
45
- devise(
46
- :database_authenticatable,
47
- :registerable,
48
- :rememberable,
49
- :recoverable,
50
- :validatable,
51
- :confirmable,
52
- :trackable
53
- );
54
-
55
44
  # users belongs to an account only...
56
45
  belongs_to :account, optional: true
57
46
 
@@ -64,20 +53,22 @@ module Lesli
64
53
  # users data extensions
65
54
  has_many :tokens
66
55
  has_many :settings
67
- has_many :sessions, class_name: 'LesliShield::User::Session'
68
56
 
69
-
70
- # Users can have many roles and too many privileges through the roles
57
+ # Authentication and authorization records are implemented by
58
+ # LesliShield. These associations remain explicit here because they
59
+ # are part of the public API used throughout the platform.
60
+ has_many :sessions, class_name: "LesliShield::User::Session"
71
61
  has_many :user_roles, class_name: "LesliShield::User::Role"
72
62
  has_many :roles, through: :user_roles, source: :role
73
63
  has_many :privileges, through: :roles
74
64
 
75
-
76
- # allow save duplicated users to execute callbacks
77
- def save(*args)
78
- super()
79
- rescue ActiveRecord::RecordNotUnique => error
80
- end
65
+ # Audit records are implemented by LesliAudit. The associations are
66
+ # declared here because requests, journals, and logs form part of the
67
+ # user API consumed by the platform.
68
+ has_many :requests, class_name: "LesliAudit::UserRequest", foreign_key: :user_id, inverse_of: :user
69
+ has_many :journals, class_name: "LesliAudit::UserJournal", foreign_key: :user_id, inverse_of: :user
70
+ has_many :logs, class_name: "LesliAudit::UserLog", foreign_key: :user_id, inverse_of: :user
71
+ has_many :account_logs, class_name: "LesliAudit::AccountLog", foreign_key: :user_id, inverse_of: :user
81
72
 
82
73
  before_create :before_create_user
83
74
 
@@ -86,7 +77,7 @@ module Lesli
86
77
  def before_create_user
87
78
  self.uid ||= loop do
88
79
  candidate = generate_resource_uid(prefix:'LID')
89
- break candidate unless self.account&.users&.exists?(uid: candidate)
80
+ break candidate unless self.class.exists?(uid: candidate)
90
81
  end
91
82
  end
92
83
 
@@ -272,12 +272,9 @@
272
272
  </div>
273
273
 
274
274
  <nav class="welcome-access" aria-label="Application access">
275
- <% unless defined?(LesliShield) %>
276
- <%= link_to("Open applications", "/apps") unless defined?(LesliDashboard) %>
277
- <% end %>
278
275
  <% if defined?(LesliShield) %>
279
- <%= link_to("Open applications", "/apps") if user_signed_in? && !defined?(LesliDashboard) %>
280
- <%= link_to("Open dashboard", "/dashboard") if user_signed_in? && defined?(LesliDashboard) %>
276
+ <%= link_to("Applications", "/lesli/apps") if user_signed_in? %>
277
+ <%= link_to("Dashboard", "/dashboard") if user_signed_in? && defined?(LesliDashboard) %>
281
278
  <%= link_to("Log out", "/logout") if user_signed_in? %>
282
279
  <%= link_to("Log in", "/login") unless user_signed_in? %>
283
280
  <% end %>
@@ -1,32 +1,18 @@
1
- <style>
2
- .error-404 .material-symbols {
3
- font-size: 8rem;
4
- color: rgb(190,190,190);
5
- }
6
- .error-404 h2 {
7
- font-size: 2rem;
8
- color: #3d211b;
9
- }
10
-
11
- .error-404 p {
12
- font-size: 1.4rem;
13
- font-weight: 300;
14
- }
15
- </style>
16
1
  <%= render(LesliView::Layout::Container.new("accounts")) do %>
17
- <section class="hero is-medium error-404">
18
- <div class="hero-body has-text-centered">
2
+ <section class="px-4 py-24 sm:px-6 sm:py-36" aria-labelledby="not-found-title">
3
+ <div class="mx-auto max-w-2xl text-center">
19
4
 
20
- <div class="mb-6">
5
+ <div class="mb-6" aria-hidden="true">
21
6
  <%= render(LesliView::Elements::Empty.new(text:nil)) %>
22
7
  </div>
23
8
 
24
- <h2 class="mb-4">404 <%= I18n.t("lesli.shared.message_error_not_found") %></h2>
9
+ <h2 id="not-found-title" class="mb-4 text-[2rem] font-semibold leading-tight text-[#3d211b]">
10
+ 404 <%= I18n.t("lesli.shared.message_error_not_found") %>
11
+ </h2>
25
12
 
26
- <p>
13
+ <p class="text-[1.4rem] font-light leading-relaxed text-neutral-700">
27
14
  <%= @message %>
28
15
  </p>
29
16
  </div>
30
17
  </section>
31
18
  <% end %>
32
-