lesli 5.1.8 → 5.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 30ffdeb6ee40bc732b261033e788707185261094e5b4a57980b547af782e8a6a
4
- data.tar.gz: ade2417c6a3f8ee2ab7eb2a4588900b9258b97189c91ece2e371c36bbc6ac54f
3
+ metadata.gz: 4ee3156ee2bbab3464a0f87d5464bf3d1bc0822eb26365ade32c75cdc40cfafa
4
+ data.tar.gz: 2ae503e439f2571995336e2f27e818ebfd5aee07d3e12b2e2cba147145e5aba2
5
5
  SHA512:
6
- metadata.gz: e6f41c34eaac31db53c9203e0981bdbe1590a3ca50a54e882abb2990a1288072f61ede2a653deac537daeec908dc0eba9d7c508543ab5e471bad53fc3dc8f58c
7
- data.tar.gz: 712b75ff2a16d3bdb84d1e37060ed1f013951ed1f9f522a43d800300d0e5b6213bba32a000f22bec5e5bd4b0c922ad59c9ddca42e35d8f3ac0a9f5ea27568475
6
+ metadata.gz: 7e50643cd811ecc96b89276098798cf66962dba64a3d69cc17135d60a751a9b7ab4d5138c218e93f85a133dc5385edef161a04654afcaa866b84c323caa42dfb
7
+ data.tar.gz: 064ac6c21289d1e15ad25d9014e3f00ed4bb0bd975775a40a65be12c633fd847f1c7ac3db610e0921bcfdd46acb5a11d1acd50c067bd7806751b76c604a61251
@@ -29,164 +29,3 @@ Building a better future, one line of code at a time.
29
29
  // · ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~
30
30
  // ·
31
31
  */
32
- /*
33
- Lesli
34
-
35
- Copyright (c) 2025, Lesli Technologies, S. A.
36
-
37
- This program is free software: you can redistribute it and/or modify
38
- it under the terms of the GNU General Public License as published by
39
- the Free Software Foundation, either version 3 of the License, or
40
- (at your option) any later version.
41
-
42
- This program is distributed in the hope that it will be useful,
43
- but WITHOUT ANY WARRANTY; without even the implied warranty of
44
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
45
- GNU General Public License for more details.
46
-
47
- You should have received a copy of the GNU General Public License
48
- along with this program. If not, see http://www.gnu.org/licenses/.
49
-
50
- Lesli · Ruby on Rails SaaS Development Framework.
51
-
52
- Made with ♥ by LesliTech
53
- Building a better future, one line of code at a time.
54
-
55
- @contact hello@lesli.tech
56
- @website https://www.lesli.tech
57
- @license GPLv3 http://www.gnu.org/licenses/gpl-3.0.en.html
58
-
59
- // · ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~
60
- // ·
61
- */
62
- /**
63
- Lesli
64
-
65
- Copyright (c) 2025, Lesli Technologies, S. A.
66
-
67
- This program is free software: you can redistribute it and/or modify
68
- it under the terms of the GNU General Public License as published by
69
- the Free Software Foundation, either version 3 of the License, or
70
- (at your option) any later version.
71
-
72
- This program is distributed in the hope that it will be useful,
73
- but WITHOUT ANY WARRANTY; without even the implied warranty of
74
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
75
- GNU General Public License for more details.
76
-
77
- You should have received a copy of the GNU General Public License
78
- along with this program. If not, see http://www.gnu.org/licenses/.
79
-
80
- Lesli - SCSS Utilities for websites, apps and web applications
81
-
82
- Made with ♥ by LesliTech
83
- Building a better future, one line of code at a time.
84
-
85
- @contact hello@lesli.tech
86
- @website https://www.lesli.tech
87
- @license GPLv3 http://www.gnu.org/licenses/gpl-3.0.en.html
88
-
89
- // · ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~
90
- // ·
91
- */
92
- /*
93
- Lesli
94
-
95
- Copyright (c) 2025, Lesli Technologies, S. A.
96
-
97
- This program is free software: you can redistribute it and/or modify
98
- it under the terms of the GNU General Public License as published by
99
- the Free Software Foundation, either version 3 of the License, or
100
- (at your option) any later version.
101
-
102
- This program is distributed in the hope that it will be useful,
103
- but WITHOUT ANY WARRANTY; without even the implied warranty of
104
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
105
- GNU General Public License for more details.
106
-
107
- You should have received a copy of the GNU General Public License
108
- along with this program. If not, see http://www.gnu.org/licenses/.
109
-
110
- Lesli - SCSS Utilities for websites, apps and web applications
111
-
112
- Made with ♥ by LesliTech
113
- Building a better future, one line of code at a time.
114
-
115
- @contact hello@lesli.tech
116
- @website https://www.lesli.tech
117
- @license GPLv3 http://www.gnu.org/licenses/gpl-3.0.en.html
118
-
119
- // · ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~
120
- // ·
121
- */
122
- /**
123
- Lesli
124
-
125
- Copyright (c) 2025, Lesli Technologies, S. A.
126
-
127
- This program is free software: you can redistribute it and/or modify
128
- it under the terms of the GNU General Public License as published by
129
- the Free Software Foundation, either version 3 of the License, or
130
- (at your option) any later version.
131
-
132
- This program is distributed in the hope that it will be useful,
133
- but WITHOUT ANY WARRANTY; without even the implied warranty of
134
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
135
- GNU General Public License for more details.
136
-
137
- You should have received a copy of the GNU General Public License
138
- along with this program. If not, see http://www.gnu.org/licenses/.
139
-
140
- Lesli - SCSS Utilities for websites, apps and web applications
141
-
142
- Made with ♥ by LesliTech
143
- Building a better future, one line of code at a time.
144
-
145
- @contact hello@lesli.tech
146
- @website https://www.lesli.tech
147
- @license GPLv3 http://www.gnu.org/licenses/gpl-3.0.en.html
148
-
149
- // · ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~
150
- // ·
151
- */
152
- body.lesli.apps.show .lesli-element-header {
153
- margin-bottom: 5rem !important;
154
- }
155
- body.lesli.apps.show .engines {
156
- gap: 24px;
157
- margin-inline-end: auto;
158
- margin-inline-start: auto;
159
- }
160
- body.lesli.apps.show .engines a {
161
- flex-basis: 318px;
162
- text-align: center;
163
- padding: 2rem 0.4rem 2.8rem;
164
- border: white 1px solid;
165
- border-radius: 6px;
166
- background-color: white;
167
- transition: all ease-in-out 0.2s;
168
- box-shadow: rgba(9, 30, 66, 0.25) 0px 4px 8px -2px, rgba(9, 30, 66, 0.08) 0px 0px 0px 1px;
169
- }
170
- body.lesli.apps.show .engines a:hover {
171
- border-color: var(--lesli-color-primary);
172
- transform: translateY(-2px);
173
- }
174
- body.lesli.apps.show .engines a.is-active {
175
- background-color: #F0F4FF;
176
- border-color: var(--lesli-color-primary);
177
- }
178
- body.lesli.apps.show .engines a svg {
179
- margin-right: 0.4rem;
180
- fill: var(--lesli-color-primary);
181
- }
182
- body.lesli.apps.show .engines a p {
183
- font-size: 14;
184
- font-family: "OpenSans";
185
- }
186
- body.lesli.apps.show .engines a span {
187
- font-weight: 600;
188
- font-size: 20px;
189
- }
190
- body.lesli.apps.show .engines a span, body.lesli.apps.show .engines a p {
191
- color: var(--lesli-color-primary);
192
- }
@@ -0,0 +1,68 @@
1
+ /*! tailwindcss v4.3.1 | MIT License | https://tailwindcss.com */
2
+ /*!
3
+
4
+ Lesli
5
+
6
+ Copyright (c) 2026, Lesli Technologies, S. A.
7
+
8
+ This program is free software: you can redistribute it and/or modify
9
+ it under the terms of the GNU General Public License as published by
10
+ the Free Software Foundation, either version 3 of the License, or
11
+ (at your option) any later version.
12
+
13
+ This program is distributed in the hope that it will be useful,
14
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ GNU General Public License for more details.
17
+
18
+ You should have received a copy of the GNU General Public License
19
+ along with this program. If not, see http://www.gnu.org/licenses/.
20
+
21
+ Lesli · Ruby on Rails SaaS Development Framework.
22
+
23
+ Made with ♥ by LesliTech
24
+ Building a better future, one line of code at a time.
25
+
26
+ @contact hello@lesli.tech
27
+ @website https://www.lesli.tech
28
+ @license GPLv3 http://www.gnu.org/licenses/gpl-3.0.en.html
29
+
30
+ // · ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~
31
+ // ·
32
+ */
33
+ @layer theme, utilities;
34
+ @layer theme {
35
+ :root, :host {
36
+ --spacing: 0.25rem;
37
+ }
38
+ }
39
+ @layer utilities {
40
+ .grid {
41
+ display: grid;
42
+ }
43
+ .hidden {
44
+ display: none;
45
+ }
46
+ .\[grid-template-columns\:repeat\(auto-fit\,minmax\(260px\,318px\)\)\] {
47
+ grid-template-columns: repeat(auto-fit,minmax(260px,318px));
48
+ }
49
+ .justify-center {
50
+ justify-content: center;
51
+ }
52
+ .gap-5 {
53
+ gap: calc(var(--spacing) * 5);
54
+ }
55
+ .py-6 {
56
+ padding-block: calc(var(--spacing) * 6);
57
+ }
58
+ .sm\:gap-6 {
59
+ @media (width >= 40rem) {
60
+ gap: calc(var(--spacing) * 6);
61
+ }
62
+ }
63
+ .sm\:py-8 {
64
+ @media (width >= 40rem) {
65
+ padding-block: calc(var(--spacing) * 8);
66
+ }
67
+ }
68
+ }
@@ -99,7 +99,7 @@ module Lesli
99
99
  engine_code = lesli_engine(:code)
100
100
 
101
101
  # Rails main host app stylesheets
102
- return 'application' if engine_code == 'root'
102
+ return stylesheet if engine_code == 'root'
103
103
 
104
104
  # Rails engines stylesheets
105
105
  "#{engine_code}/#{stylesheet}"
@@ -33,6 +33,22 @@ Building a better future, one line of code at a time.
33
33
  module Lesli
34
34
  module NavigationHelper
35
35
 
36
+ # Applications are rendered in this order on the engine selector page.
37
+ NAVIGATION_ENGINES = %i[
38
+ dashboard
39
+ admin
40
+ mailer
41
+ bell
42
+ contacts
43
+ calendar
44
+ papers
45
+ support
46
+ shield
47
+ security
48
+ audit
49
+ babel
50
+ ].freeze
51
+
36
52
  def navigation_partial
37
53
  engine = lesli_engine[:code]
38
54
  path = engine == "root" ? "partials/navigation" : "#{engine}/partials/navigation"
@@ -48,26 +64,44 @@ module Lesli
48
64
 
49
65
  # Prints a html link inside a list item
50
66
  def navigation_item(path, label, icon = nil, reload: false)
51
- content_tag(:li, { class: "navbar-item"}) do
67
+ content_tag(:li) do
52
68
  navigation_link(path, label, icon, reload:reload)
53
69
  end
54
70
  end
55
71
 
56
72
  def navigation_link(path, label, icon = nil, reload: false)
73
+ active = current_page?(path)
74
+
75
+ item_class = class_names(
76
+ "group flex items-center gap-4 rounded-2xl px-4 py-3 text-base font-medium transition focus:outline-none focus:ring-2 focus:ring-sky-500/30",
77
+ "lg:gap-2 lg:rounded-xl lg:px-4 lg:py-2 lg:text-md",
78
+ active ? "bg-sky-50 text-sky-700" : "text-slate-600 hover:bg-slate-50 hover:text-slate-950 lg:text-slate-500"
79
+ )
57
80
 
58
- #content_tag(html_element, html_options) do
59
- link_to path, data: { turbo_frame: '_top' } do
60
- # print a simple menu item (without icon)
61
- concat content_tag(:span, label, class: "text iconless") unless icon
81
+ icon_class = class_names(
82
+ icon,
83
+ "text-[24px] leading-none transition lg:text-[18px]",
84
+ active ? "text-sky-600" : "text-slate-500 group-hover:text-slate-900"
85
+ )
62
86
 
63
- # print a full menu item if icon was requested
87
+ link_to(path, { class: item_class }, data: { turbo_frame: "_top" }) do
64
88
  if icon
65
- concat content_tag(:span, nil, class: icon)
66
- concat content_tag(:span, label, class: "text")
89
+ concat content_tag(:i, nil, class: icon_class)
67
90
  end
91
+
92
+ concat content_tag(:span, label)
68
93
  end
69
94
  end
70
95
 
96
+ # Render the cards for installed engines while preserving their display order.
97
+ def navigation_engines
98
+ cards = NAVIGATION_ENGINES.filter_map do |engine|
99
+ public_send("navigation_engine_#{engine}")
100
+ end
101
+
102
+ safe_join(cards)
103
+ end
104
+
71
105
  # ADMINISTRATION
72
106
 
73
107
  # 01.01 Admin engine
@@ -306,18 +340,50 @@ module Lesli
306
340
  private
307
341
 
308
342
  # build a html link for an engine path
309
- def navigation_engine_item(title, subtitle, icon, path, is_active)
310
- # get hidden modules if there are modules to hide
343
+ def navigation_engine_item(title, subtitle, icon, path, is_active = false)
311
344
  modules_hidden = [] # Rails.application.config.lesli.dig(:modules_hidden) || []
312
345
 
313
- # stop rendering module navigation if need to hide the module
314
346
  return nil if modules_hidden.include?(path)
315
347
 
316
- # render module navigation item :)
317
- content_tag(:a, id: path.gsub("/",""), href: path, class: is_active ? "is-active" : nil, data: { turbo: false }) do
318
- lesli_svg("engine-#{icon}") << content_tag(:div) do
319
- content_tag(:span, title) << content_tag(:p, subtitle)
320
- end
348
+ card_classes = class_names(
349
+ "group relative overflow-hidden",
350
+ "flex min-h-[210px] w-full flex-col items-center justify-center",
351
+ "rounded-2xl border bg-white px-6 py-8 text-center shadow-sm",
352
+ "transition-all duration-200 ease-out",
353
+ "hover:-translate-y-0.5 hover:border-[var(--lesli-color-primary)] hover:shadow-lg",
354
+ "focus:outline-none focus-visible:ring-4 focus-visible:ring-[var(--lesli-color-primary)]/15",
355
+ is_active ? "is-active border-[var(--lesli-color-primary)] bg-sky-50" : "border-slate-200"
356
+ )
357
+
358
+ content_tag(:a, id: path.gsub("/", ""), href: path, class: card_classes, data: { turbo: false }) do
359
+ safe_join([
360
+ content_tag(:div, class: [
361
+ "mb-5 flex h-16 w-16 items-center justify-center",
362
+ "transition-all duration-200",
363
+ "group-hover:scale-105",
364
+ "[&_svg]:h-12 [&_svg]:w-12",
365
+ "[&_svg]:fill-[var(--lesli-color-primary)]",
366
+ "[&_svg_path]:fill-[var(--lesli-color-primary)]"
367
+ ].join(" ")) do
368
+ lesli_svg("engine-#{icon}")
369
+ end,
370
+
371
+ content_tag(:div, class: "px-2") do
372
+ safe_join([
373
+ content_tag(:span, title, class: "block text-xl font-semibold leading-6 tracking-tight text-[var(--lesli-color-primary)]"),
374
+ content_tag(:p, subtitle, class: "mt-2 font-[OpenSans] text-sm font-normal leading-5 text-slate-500")
375
+ ])
376
+ end,
377
+
378
+ content_tag(:span, "arrow_forward", {
379
+ class: class_names(
380
+ "material-symbols absolute right-5 top-5 text-lg text-[var(--lesli-color-primary)]",
381
+ "transition-all duration-200 group-hover:translate-x-0.5 group-hover:opacity-100",
382
+ is_active ? "opacity-100" : "opacity-0"
383
+ ),
384
+ aria: { hidden: true }
385
+ })
386
+ ])
321
387
  end
322
388
  end
323
389
  end
@@ -1,36 +1,34 @@
1
1
  <script>
2
- document.addEventListener('turbo:load', () => {
3
- const stored = localStorage.getItem('engineData')
4
- const data = stored ? JSON.parse(stored) : { engines: [] }
2
+ if (window.lesliFilterNavigationEngines) {
3
+ document.removeEventListener('turbo:load', window.lesliFilterNavigationEngines)
4
+ }
5
5
 
6
- data.engines.forEach(id => {
6
+ window.lesliFilterNavigationEngines = function() {
7
+ let data = { engines: [] }
8
+
9
+ try {
10
+ const stored = localStorage.getItem('engineData')
11
+ data = stored ? JSON.parse(stored) : data
12
+ } catch (error) {
13
+ console.warn('Unable to read the engine navigation preferences.', error)
14
+ }
15
+
16
+ const hiddenEngines = Array.isArray(data.engines) ? data.engines : []
17
+
18
+ hiddenEngines.forEach(id => {
7
19
  const el = document.getElementById(id.replace("lesli"))
8
- if (el) el.style.display = 'none'
20
+ if (el) el.hidden = true
9
21
  })
10
- })
22
+ }
23
+
24
+ document.addEventListener('turbo:load', window.lesliFilterNavigationEngines)
11
25
  </script>
12
- <%
13
- @navigation_engines = [
14
- :dashboard,
15
- :admin,
16
- :mailer,
17
- :bell,
18
- :contacts,
19
- :calendar,
20
- :papers,
21
- :support,
22
- :shield,
23
- :security,
24
- :audit,
25
- :babel
26
- ]
27
- %>
28
26
 
29
27
  <%= render(LesliView::Layout::Container.new("lesli-apps", dashboard: false)) do %>
30
28
  <%= render("lesli_dashboard/dashboards/shared/header") if defined?(LesliDashboard) %>
31
- <section class="columns is-multiline is-mobile is-centered engines">
32
- <% @navigation_engines.each do |engine| %>
33
- <%= public_send("navigation_engine_#{engine}") %>
34
- <% end %>
29
+ <section class="lesli-engines py-6 sm:py-8">
30
+ <div class="grid justify-center gap-5 sm:gap-6 [grid-template-columns:repeat(auto-fit,minmax(260px,318px))]">
31
+ <%= navigation_engines %>
32
+ </div>
35
33
  </section>
36
34
  <% end %>
@@ -36,7 +36,8 @@ Building a better future, one line of code at a time.
36
36
  <html lang="en">
37
37
  <head>
38
38
  <%= render(partial: "lesli/partials/application-lesli-head") %>
39
- <%= stylesheet_link_tag(lesli_asset_path(:lesli_shield, public_app_controller), media: "all") %>
39
+ <%= stylesheet_link_tag(lesli_asset_path(:lesli_assets, 'public.tailwind'), media: "all") %>
40
+ <%= stylesheet_link_tag(lesli_asset_path(:lesli_shield, public_app_controller.concat(".tailwind")), media: "all") %>
40
41
  <%# Disable back button in browser after Logout using JavaScript %>
41
42
  <% if flash[:logout] == true %>
42
43
  <script>
@@ -50,8 +50,9 @@ Building a better future, one line of code at a time.
50
50
 
51
51
 
52
52
  <%# Loading stylesheets from engines %>
53
- <%= stylesheet_link_tag(lesli_asset_path(:lesli_assets), media: "all") %>
54
- <%= stylesheet_link_tag(lesli_asset_path(), media: "all") %>
53
+ <%= stylesheet_link_tag(lesli_asset_path(:lesli_assets, 'application.tailwind'), media: "all") %>
54
+ <%= stylesheet_link_tag(lesli_asset_path(:lesli_assets, 'view.tailwind'), media: "all") %>
55
+ <%= stylesheet_link_tag(lesli_asset_path(nil,'application.tailwind'), media: "all") %>
55
56
 
56
57
 
57
58
  <%# Loading javascripts from engines %>
@@ -2,7 +2,7 @@
2
2
 
3
3
  Lesli
4
4
 
5
- Copyright (c) 2025, Lesli Technologies, S. A.
5
+ Copyright (c) 2026, Lesli Technologies, S. A.
6
6
 
7
7
  This program is free software: you can redistribute it and/or modify
8
8
  it under the terms of the GNU General Public License as published by
@@ -30,92 +30,116 @@ Building a better future, one line of code at a time.
30
30
  // ·
31
31
  %>
32
32
 
33
- <%# Get the navigation partial for every engine %>
34
- <% engine_navigation_partial = navigation_partial %>
35
-
36
- <header class="lesli-application-header">
37
- <div class="container" x-data="{ navActive: false }">
38
- <nav class="navbar" role="navigation" aria-label="main navigation">
39
- <div class="navbar-brand">
40
- <a
41
- class="navbar-item p-0"
42
- href="<%= defined?(LesliDashboard) ? '/dashboard' : '/' %>">
43
- <%= customization_instance_logo_tag(logo: "app-logo", options: { alt: "Lesli app logo" }) %>
44
- </a>
45
- <button
46
- class="navbar-burger"
47
- type="button"
48
- aria-label="menu"
49
- aria-expanded="false"
50
- @click="navActive = !navActive"
51
- :class="{ 'is-active': navActive }">
52
- <span aria-hidden="true"></span>
53
- <span aria-hidden="true"></span>
54
- <span aria-hidden="true"></span>
55
- <span aria-hidden="true"></span>
56
- </button>
33
+ <%
34
+ engine_navigation_partial = navigation_partial
35
+ dashboard_path = defined?(LesliDashboard) ? lesli_dashboard.root_path : main_app.root_path
36
+ notifications_count = @lesli&.dig(:notifications).to_i
37
+
38
+ header_action_class = "relative flex h-10 w-10 shrink-0 items-center justify-center rounded-xl text-slate-500 transition-colors hover:bg-slate-100 hover:text-slate-950 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-500/40"
39
+ logout_action_class = "relative flex h-10 w-10 shrink-0 items-center justify-center rounded-xl text-slate-500 transition-colors hover:bg-red-50 hover:text-red-600 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-red-500/30"
40
+
41
+ dashboard_label = I18n.t("lesli.header.dashboard", default: "Lesli Dashboard")
42
+ notifications_label = if notifications_count.positive?
43
+ I18n.t("lesli.header.notifications_unread", count: notifications_count, default: "%{count} unread notifications")
44
+ else
45
+ I18n.t("lesli.header.notifications", default: "Notifications")
46
+ end
47
+ applications_label = I18n.t("lesli.header.applications", default: "Applications")
48
+ profile_label = I18n.t("lesli.header.profile", default: "Profile")
49
+ logout_label = I18n.t("lesli.header.logout", default: "Logout")
50
+
51
+ action_class = ->(path) {
52
+ class_names(
53
+ header_action_class,
54
+ current_page?(path) && "bg-sky-50 text-sky-700"
55
+ )
56
+ }
57
+ %>
58
+
59
+ <header class="border-b border-slate-200/80 bg-white">
60
+ <div class="mx-auto flex h-20 max-w-7xl items-center justify-between px-4 sm:px-6 lg:px-8">
61
+
62
+ <!-- Logo -->
63
+ <%= link_to dashboard_path,
64
+ class: "flex items-center",
65
+ aria: { label: dashboard_label },
66
+ data: { turbo_frame: "_top" } do %>
67
+
68
+ <div class="flex items-center [&_img]:h-12 [&_img]:w-auto">
69
+ <%= customization_instance_logo_tag(
70
+ logo: "app-logo",
71
+ options: { alt: "Lesli app logo" }
72
+ ) %>
57
73
  </div>
58
- <div class="navbar-menu" :class="{ 'is-active': navActive }">
59
- <div class="navbar-start is-flex is-flex-grow-1 is-justify-content-center">
60
- <%# ["favorite","sentiment_excited","person_pin_circle","manage_accounts"].each do |icon| %>
61
- <% [].each do |icon| %>
62
- <div class="navbar-item">
63
- <a data-turbo-frame="_top" href="/babel/labels" class="icon py-4 px-5">
64
- <span class="material-symbols-outlined">
65
- <%= icon %>
66
- </span>
67
- </a>
68
- </div>
74
+ <% end %>
75
+
76
+ <%# Application actions %>
77
+ <nav class="flex items-center gap-2" aria-label="Application actions">
78
+
79
+ <% if defined?(LesliBell) %>
80
+ <%= link_to lesli_bell.notifications_path,
81
+ class: action_class.call(lesli_bell.notifications_path),
82
+ aria: {
83
+ label: notifications_label,
84
+ current: current_page?(lesli_bell.notifications_path) ? "page" : nil
85
+ },
86
+ title: notifications_label,
87
+ data: { turbo_frame: "_top" } do %>
88
+
89
+ <i class="<%= notifications_count.positive? ? 'ri-notification-3-fill text-sky-600' : 'ri-notification-3-line' %> text-[24px] leading-none" aria-hidden="true"></i>
90
+
91
+ <% if notifications_count.positive? %>
92
+ <span class="absolute -right-0 -top-0 flex min-h-4 min-w-4 items-center justify-center rounded-full bg-sky-600 px-1 text-[9px] font-semibold leading-none text-white ring-2 ring-white" aria-hidden="true">
93
+ <%= notifications_count > 99 ? "99+" : notifications_count %>
94
+ </span>
69
95
  <% end %>
70
- </div>
71
- <div class="navbar-end is-flex is-justify-content-center">
72
-
73
- <!-- notifications -->
74
- <div class="navbar-item">
75
- <% if defined?(LesliBell) %>
76
- <%= link_to(
77
- lesli_bell.notifications_path,
78
- :class => "header-indicator",
79
- :data => { turbo_frame: '_top' }) do %>
80
- <% if @lesli[:notifications] > 0 %>
81
- <span class="ri-notification-3-fill has-text-primary"></span>
82
- <% else %>
83
- <span class="ri-notification-3-line"></span>
84
- <% end %>
85
- <% end %>
86
- <% end %>
87
- </div>
88
-
89
- <!-- app selector -->
90
- <div class="navbar-item">
91
- <%= navigation_link(lesli.apps_path, nil, "ri-apps-2-line") %>
92
- </div>
93
-
94
- <!-- user profile -->
95
- <div class="navbar-item">
96
- <%= navigation_link(lesli.apps_path, nil, "ri-user-smile-line") %>
97
- </div>
98
-
99
- <!-- logout from session -->
100
- <div class="navbar-item">
101
- <%= navigation_link(main_app.destroy_user_session_path, nil, "ri-logout-circle-r-line") if defined?(LesliShield) %>
102
- </div>
103
- </div>
104
- <div class="navbar-end is-hidden-desktop">
105
- <%= render(engine_navigation_partial) if engine_navigation_partial %>
106
- </div>
107
- </div>
96
+ <% end %>
97
+ <% end %>
98
+
99
+ <%= link_to lesli.apps_path,
100
+ class: action_class.call(lesli.apps_path),
101
+ aria: {
102
+ label: applications_label,
103
+ current: current_page?(lesli.apps_path) ? "page" : nil
104
+ },
105
+ title: applications_label,
106
+ data: { turbo_frame: "_top" } do %>
107
+ <i class="ri-apps-2-line text-[24px] leading-none" aria-hidden="true"></i>
108
+ <% end %>
109
+
110
+ <% if defined?(LesliAdmin) %>
111
+ <%= link_to lesli_admin.profile_path,
112
+ class: action_class.call(lesli_admin.profile_path),
113
+ aria: {
114
+ label: profile_label,
115
+ current: current_page?(lesli_admin.profile_path) ? "page" : nil
116
+ },
117
+ title: profile_label,
118
+ data: { turbo_frame: "_top" } do %>
119
+ <i class="ri-user-smile-line text-[24px] leading-none" aria-hidden="true"></i>
120
+ <% end %>
121
+ <% end %>
122
+
123
+ <% if defined?(LesliShield) %>
124
+ <%= link_to main_app.destroy_user_session_path,
125
+ class: logout_action_class,
126
+ aria: { label: logout_label },
127
+ title: logout_label do %>
128
+ <i class="ri-logout-circle-r-line text-[24px] leading-none" aria-hidden="true"></i>
129
+ <% end %>
130
+ <% end %>
131
+
108
132
  </nav>
109
133
  </div>
110
134
  </header>
111
135
 
112
136
  <%# Build the engine navigation %>
113
137
  <% if engine_navigation_partial %>
114
- <nav class="navbar lesli-application-navigation is-hidden-touch">
115
- <div class="navbar-menu">
116
- <div class="navbar-start is-flex-grow-1 is-justify-content-center">
138
+ <nav class="border-b border-slate-200/80 bg-white" aria-label="Module navigation">
139
+ <div class="mx-auto max-w-7xl overflow-x-auto px-4 sm:px-6 lg:px-8">
140
+ <ul class="flex min-h-14 w-max min-w-full items-center justify-start gap-1 py-2 sm:justify-center">
117
141
  <%= render(engine_navigation_partial) %>
118
- </div>
142
+ </ul>
119
143
  </div>
120
144
  </nav>
121
145
  <% end %>
@@ -1,6 +1,15 @@
1
1
  ---
2
2
  :en:
3
3
  lesli:
4
+ header:
5
+ applications: Applications
6
+ dashboard: Lesli Dashboard
7
+ logout: Logout
8
+ notifications: Notifications
9
+ notifications_unread:
10
+ one: "%{count} unread notification"
11
+ other: "%{count} unread notifications"
12
+ profile: Profile
4
13
  shared:
5
14
  message_error_not_found: Not found
6
15
  message_error_resource_not_found: The resource you are looking for was not found.
@@ -1,6 +1,15 @@
1
1
  ---
2
2
  :es:
3
3
  lesli:
4
+ header:
5
+ applications: Aplicaciones
6
+ dashboard: Panel de Lesli
7
+ logout: Cerrar sesión
8
+ notifications: Notificaciones
9
+ notifications_unread:
10
+ one: "%{count} notificación sin leer"
11
+ other: "%{count} notificaciones sin leer"
12
+ profile: Perfil
4
13
  shared:
5
14
  message_error_not_found: ":lesli.shared.message_error_not_found:"
6
15
  message_error_resource_not_found: El recurso que estás buscando no fue encontrado
data/lib/lesli/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module Lesli
2
- VERSION = "5.1.8"
3
- BUILD = "1781844207"
2
+ VERSION = "5.1.9"
3
+ BUILD = "1789351263"
4
4
  end
data/lib/lesli.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require "rails-i18n"
2
+
1
3
  require "lesli/configuration"
2
4
  require "lesli/version"
3
5
  require "lesli/courier"
@@ -34,41 +34,24 @@ Building a better future, one line of code at a time.
34
34
  namespace :lesli do
35
35
  namespace :db do
36
36
 
37
- desc "Drop, create, migrate, seed & setup the Lesli database (development only)"
38
- task :reset => :environment do |task, args|
37
+ desc "Drop, create, migrate, seed & configure the Lesli database (development only)"
38
+ task :rebuild => :environment do |task, args|
39
39
  drop()
40
40
  create()
41
41
  migrate()
42
42
  seed()
43
- setup()
43
+ configure()
44
44
  status()
45
45
  end
46
46
 
47
- desc "Create, migrate, seed & setup the Lesli database (development only)"
48
- task :dev => :environment do |task, args|
49
- create()
50
- migrate()
51
- seed()
52
- setup()
53
- status()
54
- end
55
-
56
- desc "Migrate, setup && user the Lesli database"
57
- task :deploy => :environment do |task, args|
58
- create()
59
- migrate()
60
- setup()
61
- status()
62
- end
63
-
64
- desc "Migrate & setup the Lesli database"
65
- task :upgrade => :environment do |task, args|
66
- migrate()
67
- setup()
47
+ desc "Migrate, configure && user the Lesli database"
48
+ task :prepare => :environment do |task, args|
49
+ prepare()
50
+ configure()
68
51
  status()
69
52
  end
70
53
 
71
- desc "Seed & setup Lesli database"
54
+ desc "Seed & configure Lesli database"
72
55
  task :seed => :environment do |task, args|
73
56
  seed()
74
57
  status()
@@ -79,7 +62,7 @@ namespace :lesli do
79
62
  def drop
80
63
 
81
64
  # do not execute this task if we are at production level
82
- return if Rails.env.production?
65
+ raise "Cannot drop database in production" if Rails.env.production?
83
66
 
84
67
  # print a message to let the users show the action running
85
68
  Termline.m("Drop the Lesli database (development only)")
@@ -112,6 +95,15 @@ namespace :lesli do
112
95
  Rake::Task['db:migrate'].invoke
113
96
  end
114
97
 
98
+ # Create the Lesli database (development only)
99
+ def prepare
100
+
101
+ # print a message to let the users show the action running
102
+ Termline.info("Prepare the Lesli database")
103
+
104
+ Rake::Task['db:prepare'].invoke
105
+ end
106
+
115
107
  def seed
116
108
 
117
109
  # print a message to let the users show the action running
@@ -129,7 +121,7 @@ namespace :lesli do
129
121
  Termline.br(2)
130
122
  end
131
123
 
132
- def setup
124
+ def configure
133
125
 
134
126
  # print a message to let the users show the action running
135
127
  Termline.info("Setup the Lesli database")
@@ -137,7 +129,7 @@ namespace :lesli do
137
129
  # scan rails routes to build the controllers index
138
130
  Rake::Task['lesli:resources:build'].invoke
139
131
 
140
- Lesli::Account.all.each do |account|
132
+ Lesli::Account.find_each do |account|
141
133
  account.initialize_account
142
134
  account.initialize_engines
143
135
  end
data/readme.md CHANGED
@@ -1,14 +1,20 @@
1
1
  <div align="center">
2
- <img width="150" alt="Lesli logo" src="./app/assets/images/lesli/lesli-logo.svg" />
3
- <h3 align="center">The Open-Source SaaS Development Framework for Ruby on Rails.</h3>
2
+ <h1 align="center">
3
+ <img
4
+ width="150"
5
+ alt="Lesli"
6
+ src="./app/assets/images/lesli/lesli-logo.svg" />
7
+ </h1>
8
+ <h3>The Open-Source SaaS Development Framework for Ruby on Rails.</h3>
4
9
  </div>
5
10
 
6
11
  <br />
7
- <hr/>
8
12
 
9
13
  <div align="center">
10
- <a target="_blank" href="https://github.com/LesliTech/Lesli/actions">
11
- <img alt="Tests passing" src="https://img.shields.io/badge/Tests-passing-green?style=for-the-badge&logo=github">
14
+ <a target="_blank" href="https://github.com/LesliTech/Lesli/actions/workflows/lesli-ci-tests.yaml">
15
+ <img
16
+ alt="Lesli test status"
17
+ src="https://img.shields.io/github/actions/workflow/status/LesliTech/Lesli/lesli-ci-tests.yaml?branch=master&style=for-the-badge&logo=github&label=tests">
12
18
  </a>
13
19
  <a target="_blank" href="https://rubygems.org/gems/lesli">
14
20
  <img alt="Gem Version" src="https://img.shields.io/gem/v/lesli?style=for-the-badge&logo=ruby">
@@ -21,114 +27,173 @@
21
27
  </a>
22
28
  </div>
23
29
 
24
- <hr/>
25
30
  <br />
26
31
 
27
32
  <div align="center">
28
33
  <img
29
34
  style="width:100%;max-width:800px;border-radius:6px;"
30
- alt="Lesli screenshot" src="./docs/images/screenshot.png" />
35
+ alt="Lesli application dashboard"
36
+ src="./docs/images/screenshot.png" />
31
37
  </div>
32
38
 
33
- ---
39
+ <br />
34
40
 
35
41
  ## Introduction
36
42
 
37
43
  Lesli is an open-source framework built to create scalable, secure, and modular software products.
38
44
 
39
- Built on top of **Ruby on Rails, PostgreSQL, Hotwire, and modern frontend tooling**, Lesli provides the infrastructure, architecture, and reusable components needed to build production-ready SaaS platforms faster.
40
-
41
- Lesli is not a starter kit, admin panel, or boilerplate.
45
+ Built on **Ruby on Rails, Hotwire, and Tailwind CSS**, with support for **SQLite and PostgreSQL**, Lesli provides the infrastructure, architecture, and reusable components needed to build production-ready SaaS platforms faster.
42
46
 
43
- It is a **modular foundation** that allows you to build applications using independent engines, shared infrastructure, and proven SaaS patterns.
47
+ Lesli is not a starter kit, admin panel, or boilerplate. It is a **modular foundation** that allows you to build applications using independent engines, shared infrastructure, and proven SaaS patterns.
44
48
 
45
- With Lesli, you focus on features not infrastructure.
49
+ With Lesli, you focus on features—not infrastructure.
46
50
 
47
- ---
51
+ <br />
48
52
 
49
53
  ## Why Lesli?
50
54
 
51
- - Modular architecture powered by standard Rails engines
52
- - Flexible authentication, detailed privileges, and access control
53
- - System auditing with activities and version tracking
54
- - Attachments that can be added to any resource
55
- - Full-text search across modules and records
56
- - Multi-language support for both core and custom modules
57
- - Designed for long-term SaaS scalability and maintainability
55
+ Lesli core and its official engines provide:
58
56
 
59
- ---
57
+ - Modular architecture powered by standard Rails engines
58
+ - Flexible authentication, detailed privileges, and access control
59
+ - System auditing with activities and version tracking
60
+ - Attachments that can be added to any resource
61
+ - Full-text search across modules and records
62
+ - Multi-language support for both core and custom modules
63
+ - An architecture designed for long-term SaaS scalability and maintainability
64
+
65
+ <br />
66
+
67
+ ## Try Lesli
68
+
69
+ - [Try the online demo](https://demo.lesli.dev/)
70
+ - [Run the Docker demo](https://github.com/LesliTech/lesli-docker-demo)
71
+
72
+ <br />
60
73
 
61
74
  ## Quick Start
62
75
 
63
- ```bash
64
- # Create a new app using the Lesli template
65
- rails new LesliDemo -m lesli_demo.rb --skip-bundle
76
+ ### Requirements
77
+
78
+ - Ruby 3.2 or newer
79
+ - Rails 8.1
80
+ - SQLite, included in the default Rails application
81
+
82
+ Optional tooling:
83
+
84
+ - PostgreSQL for applications that prefer it over SQLite
85
+ - Node.js 20.x for advanced frontend development tasks
86
+
87
+ The default Lesli application can be built and run using only Rails and the Lesli gems.
88
+
89
+ ### Create the application
90
+
91
+ Create a Rails application using Rails' default SQLite database:
92
+
93
+ ```shell
94
+ rails new LesliApp
95
+ cd LesliApp
66
96
  ```
67
97
 
68
- ### Manual installation
98
+ ### Install Lesli
99
+
100
+ Add the gem and run the installation generator:
69
101
 
70
102
  ```shell
71
- # Add Lesli
72
103
  bundle add lesli
104
+ bin/rails generate lesli:install
105
+ ```
73
106
 
74
- # Install Lesli
75
- rails generate lesli:install
107
+ ### Prepare the database
76
108
 
77
- # Setup database
78
- rake lesli:db:reset
109
+ Prepare the development database and verify the installation:
79
110
 
80
- # Start server
81
- rails server
111
+ ```shell
112
+ bin/rails lesli:db:prepare
113
+ bin/rails lesli:status
82
114
  ```
83
115
 
84
- ### Development
116
+ > [!WARNING]
117
+ > `bin/rails lesli:db:rebuild` drops, recreates, migrates, and seeds the database. Use it only when you intentionally want to replace the current development data.
118
+
119
+ ### Start the server
85
120
 
86
121
  ```shell
87
- # clone the lesli repo inside your engine folder: RailsApp/engines
88
- git clone https://github.com/LesliTech/Lesli.git
122
+ bin/rails server
123
+ ```
89
124
 
90
- # Load Lesli as a Gem
91
- gem "lesli", path: "engines/Lesli"
125
+ Then visit [http://127.0.0.1:3000/login](http://127.0.0.1:3000/login).
92
126
 
93
- # Install the necessary Gems to run Lesli
94
- bundle install
127
+ <br />
128
+
129
+ ## Development
95
130
 
96
- # Generate Lesli initializer
97
- rails generate lesli:install
131
+ ### Development template
98
132
 
99
- # Hard reset Database for development:
100
- rake lesli:db:reset
133
+ To generate a complete demo application from the current development source:
101
134
 
102
- # Run default Rails server
103
- rails s
104
- rails s --binding=0.0.0.0
105
- rails s --environment=test
106
- rails s --environment=development
107
- RAILS_SERVE_STATIC_FILES=true rails s --environment=production
135
+ ```shell
136
+ rails new LesliDemo \
137
+ --skip-bundle \
138
+ -m https://raw.githubusercontent.com/LesliTech/Lesli/master/templates/template-dev.rb
108
139
  ```
109
140
 
110
- ---
141
+ > [!NOTE]
142
+ > The development template clones the mutable `master` branch of every Lesli repository. Use released gems for production applications.
143
+
144
+ ### Manual local setup
145
+
146
+ To work on Lesli from a host Rails application, clone it into the application's `engines` directory:
147
+
148
+ ```shell
149
+ cd RailsApp
150
+ mkdir -p engines
151
+ git clone https://github.com/LesliTech/Lesli.git engines/Lesli
152
+ ```
153
+
154
+ Reference the local engine from the host application's `Gemfile`:
155
+
156
+ ```ruby
157
+ gem "lesli", path: "engines/Lesli"
158
+ ```
159
+
160
+ Install and initialize the local engine:
161
+
162
+ ```shell
163
+ bundle install
164
+ bin/rails generate lesli:install
165
+ bin/rails lesli:db:prepare
166
+ bin/rails server
167
+ ```
111
168
 
112
- ### Demo
169
+ For additional server configurations, use the standard Rails server options:
113
170
 
114
- * [online demo](https://demo.lesli.dev/)
115
- * [docker demo](https://github.com/LesliTech/lesli-docker-demo)
171
+ ```shell
172
+ bin/rails server --binding=0.0.0.0
173
+ bin/rails server --environment=development
174
+ ```
175
+
176
+ <br />
116
177
 
178
+ ## Documentation
117
179
 
118
- ### Documentation
119
- * [website](https://www.lesli.dev/)
120
- * [documentation](https://www.lesli.dev/lesli/)
180
+ - [Lesli website](https://www.lesli.dev/)
181
+ - [Documentation](https://www.lesli.dev/lesli/)
182
+ - [Contribution guide](./docs/contributing/github.md)
183
+ - [Releases and changelog](https://github.com/LesliTech/Lesli/releases)
121
184
 
185
+ <br />
186
+
187
+ ## Community
122
188
 
123
- ### Connect with Lesli
189
+ - [X: @LesliTech](https://x.com/LesliTech)
190
+ - [hello@lesli.tech](mailto:hello@lesli.tech)
191
+ - [https://www.lesli.tech](https://www.lesli.tech)
124
192
 
125
- * [@LesliTech](https://x.com/LesliTech)
126
- * [hello@lesli.tech](hello@lesli.tech)
127
- * [https://www.lesli.tech](https://www.lesli.tech)
193
+ <br />
128
194
 
195
+ ## License
129
196
 
130
- ### License
131
- -------
132
197
  Copyright (c) 2026, Lesli Technologies, S. A.
133
198
 
134
199
  This program is free software: you can redistribute it and/or modify
@@ -142,18 +207,16 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
142
207
  GNU General Public License for more details.
143
208
 
144
209
  You should have received a copy of the GNU General Public License
145
- along with this program. If not, see http://www.gnu.org/licenses/.
210
+ along with this program. If not, see [https://www.gnu.org/licenses/](https://www.gnu.org/licenses/).
146
211
 
147
- <hr />
148
- <br />
149
- <br />
212
+ The complete license text is available in the [license file](./license).
150
213
 
151
- <div align="center" class="has-text-centered">>
152
- <img width="80" alt="Lesli logo" src="https://cdn.lesli.tech/lesli/brand/app-icon.svg" />
153
- <h3 align="center" class="mt-0">
154
- The Open-Source SaaS Development Framework for Ruby on Rails.
155
- </h3>
156
- </div>
214
+ ---
157
215
 
158
216
  <br />
159
217
  <br />
218
+
219
+ <div align="center">
220
+ <img width="80" alt="Lesli icon" src="https://cdn.lesli.tech/lesli/brand/app-icon.svg" />
221
+ <h3 align="center">The Open-Source SaaS Development Framework for Ruby on Rails.</h3>
222
+ </div>
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lesli
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.1.8
4
+ version: 5.1.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - The Lesli Development Team
@@ -23,6 +23,20 @@ dependencies:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
25
  version: '8.1'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rails-i18n
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '8.1'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '8.1'
26
40
  - !ruby/object:Gem::Dependency
27
41
  name: turbo-rails
28
42
  requirement: !ruby/object:Gem::Requirement
@@ -161,6 +175,7 @@ files:
161
175
  - Rakefile
162
176
  - app/assets/images/lesli/lesli-logo.svg
163
177
  - app/assets/stylesheets/lesli/application.css
178
+ - app/assets/stylesheets/lesli/application.tailwind.css
164
179
  - app/controllers/lesli/abouts_controller.rb
165
180
  - app/controllers/lesli/application_controller.rb
166
181
  - app/controllers/lesli/application_devise_controller.rb