lesli_bell 1.1.1 → 1.1.2

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: 230a04119c05f73f87f6fefcffc2f930301763388864f34fa6feb3bcf510598b
4
- data.tar.gz: 654752028cdbe7d683f2c30a8060156f73b5e4a78de4d6dec07a5b1916cc3aa8
3
+ metadata.gz: a77e641c1b3a6cb59f036389ff8f936266305fa7f95068adff945cb95da7e1f7
4
+ data.tar.gz: e7f37aa94d7be4bd29b43cdc73c61ab7017d90a7bd0559c2ab44b93c94e43864
5
5
  SHA512:
6
- metadata.gz: f6d3dd2e9d2a37278dc122df57f2ab02b344f1c56d92167e4c4ae5eecef40badfda09cc39d8d4716ecccb72b144e111a7c0d54e9c95b300759f9784d630843fc
7
- data.tar.gz: 77dbd210d847f72c9c923f1c010c73ee8453e83f544876a2df1a03edf3af3528530eccb881c0fb23a404fac81da316ea8b0821215818ff7f415af0422c58f8cd
6
+ metadata.gz: '0682ccc5d63449e496b659352b244eca8785bae27ecb679580c52f880e9850a457b2b8b1c98b173cbee3b8a77187491f7d49d57b7f2fc794e7177a229c1bf1e9'
7
+ data.tar.gz: 727e0dde63a506b1f92a3432c6c55e4230577a19d1879371f45fe95347e3d8e0f8b03152c8287f74f68a8cc1a2fb4947d54e0da026a9d5d204b686084bb0d366
data/Rakefile CHANGED
@@ -4,3 +4,6 @@ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
4
  load "rails/tasks/engine.rake"
5
5
 
6
6
  require "bundler/gem_tasks"
7
+
8
+ desc "Run engine tests through the dummy Rails application"
9
+ task test: "app:test"
@@ -1,4 +1,476 @@
1
- /*!***********************************************************************************************************************************************************!*\
2
- !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[0].use[1]!./node_modules/sass-loader/dist/cjs.js!../LesliBell/lib/scss/application.scss ***!
3
- \***********************************************************************************************************************************************************/
4
-
1
+ /*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
2
+ @layer properties;
3
+ @layer theme, utilities;
4
+ @layer theme {
5
+ :root, :host {
6
+ --color-sky-50: oklch(97.7% 0.013 236.62);
7
+ --color-sky-100: oklch(95.1% 0.026 236.824);
8
+ --color-sky-200: oklch(90.1% 0.058 230.902);
9
+ --color-sky-500: oklch(68.5% 0.169 237.323);
10
+ --color-sky-700: oklch(50% 0.134 242.749);
11
+ --color-sky-800: oklch(44.3% 0.11 240.79);
12
+ --color-slate-50: oklch(98.4% 0.003 247.858);
13
+ --color-slate-100: oklch(96.8% 0.007 247.896);
14
+ --color-slate-200: oklch(92.9% 0.013 255.508);
15
+ --color-slate-400: oklch(70.4% 0.04 256.788);
16
+ --color-slate-500: oklch(55.4% 0.046 257.417);
17
+ --color-slate-600: oklch(44.6% 0.043 257.281);
18
+ --color-slate-800: oklch(27.9% 0.041 260.031);
19
+ --color-white: #fff;
20
+ --spacing: 0.25rem;
21
+ --text-xs: 0.75rem;
22
+ --text-xs--line-height: calc(1 / 0.75);
23
+ --text-sm: 0.875rem;
24
+ --text-sm--line-height: calc(1.25 / 0.875);
25
+ --text-base: 1rem;
26
+ --text-base--line-height: calc(1.5 / 1);
27
+ --text-lg: 1.125rem;
28
+ --text-lg--line-height: calc(1.75 / 1.125);
29
+ --text-xl: 1.25rem;
30
+ --text-xl--line-height: calc(1.75 / 1.25);
31
+ --font-weight-medium: 500;
32
+ --font-weight-semibold: 600;
33
+ --radius-md: 0.375rem;
34
+ --radius-lg: 0.5rem;
35
+ --radius-2xl: 1rem;
36
+ --default-transition-duration: 150ms;
37
+ --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
38
+ }
39
+ }
40
+ @layer utilities {
41
+ .absolute {
42
+ position: absolute;
43
+ }
44
+ .relative {
45
+ position: relative;
46
+ }
47
+ .inset-y-0 {
48
+ inset-block: 0px;
49
+ }
50
+ .top-3\.5 {
51
+ top: calc(var(--spacing) * 3.5);
52
+ }
53
+ .right-4 {
54
+ right: calc(var(--spacing) * 4);
55
+ }
56
+ .left-0 {
57
+ left: 0px;
58
+ }
59
+ .mt-1 {
60
+ margin-top: var(--spacing);
61
+ }
62
+ .mt-1\.5 {
63
+ margin-top: calc(var(--spacing) * 1.5);
64
+ }
65
+ .line-clamp-2 {
66
+ overflow: hidden;
67
+ display: -webkit-box;
68
+ -webkit-box-orient: vertical;
69
+ -webkit-line-clamp: 2;
70
+ }
71
+ .block {
72
+ display: block;
73
+ }
74
+ .flex {
75
+ display: flex;
76
+ }
77
+ .inline-flex {
78
+ display: inline-flex;
79
+ }
80
+ .size-8 {
81
+ width: calc(var(--spacing) * 8);
82
+ height: calc(var(--spacing) * 8);
83
+ }
84
+ .size-9 {
85
+ width: calc(var(--spacing) * 9);
86
+ height: calc(var(--spacing) * 9);
87
+ }
88
+ .w-1 {
89
+ width: var(--spacing);
90
+ }
91
+ .w-fit {
92
+ width: fit-content;
93
+ }
94
+ .min-w-0 {
95
+ min-width: 0px;
96
+ }
97
+ .flex-1 {
98
+ flex: 1;
99
+ }
100
+ .shrink-0 {
101
+ flex-shrink: 0;
102
+ }
103
+ .flex-wrap {
104
+ flex-wrap: wrap;
105
+ }
106
+ .items-center {
107
+ align-items: center;
108
+ }
109
+ .items-start {
110
+ align-items: flex-start;
111
+ }
112
+ .justify-center {
113
+ justify-content: center;
114
+ }
115
+ .gap-1 {
116
+ gap: var(--spacing);
117
+ }
118
+ .gap-2 {
119
+ gap: calc(var(--spacing) * 2);
120
+ }
121
+ .gap-3 {
122
+ gap: calc(var(--spacing) * 3);
123
+ }
124
+ .gap-x-2 {
125
+ column-gap: calc(var(--spacing) * 2);
126
+ }
127
+ .gap-y-1 {
128
+ row-gap: var(--spacing);
129
+ }
130
+ :where(.divide-y > :not(:last-child)) {
131
+ --tw-divide-y-reverse: 0;
132
+ border-bottom-style: var(--tw-border-style);
133
+ border-top-style: var(--tw-border-style);
134
+ border-top-width: calc(1px * var(--tw-divide-y-reverse));
135
+ border-bottom-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
136
+ }
137
+ :where(.divide-slate-200 > :not(:last-child)) {
138
+ border-color: var(--color-slate-200);
139
+ }
140
+ .overflow-hidden {
141
+ overflow: hidden;
142
+ }
143
+ .rounded-2xl {
144
+ border-radius: var(--radius-2xl);
145
+ }
146
+ .rounded-full {
147
+ border-radius: calc(infinity * 1px);
148
+ }
149
+ .rounded-lg {
150
+ border-radius: var(--radius-lg);
151
+ }
152
+ .rounded-md {
153
+ border-radius: var(--radius-md);
154
+ }
155
+ .border {
156
+ border-style: var(--tw-border-style);
157
+ border-width: 1px;
158
+ }
159
+ .border-slate-200 {
160
+ border-color: var(--color-slate-200);
161
+ }
162
+ .border-transparent {
163
+ border-color: transparent;
164
+ }
165
+ .bg-danger-50 {
166
+ background-color: var(--lesli-color-danger-50);
167
+ }
168
+ .bg-danger-500 {
169
+ background-color: var(--lesli-color-danger-500);
170
+ }
171
+ .bg-info-50 {
172
+ background-color: var(--lesli-color-info-50);
173
+ }
174
+ .bg-info-500 {
175
+ background-color: var(--lesli-color-info-500);
176
+ }
177
+ .bg-neutral-50 {
178
+ background-color: var(--lesli-color-neutral-50);
179
+ }
180
+ .bg-neutral-400 {
181
+ background-color: var(--lesli-color-neutral-400);
182
+ }
183
+ .bg-sky-50 {
184
+ background-color: var(--color-sky-50);
185
+ }
186
+ .bg-slate-100 {
187
+ background-color: var(--color-slate-100);
188
+ }
189
+ .bg-success-50 {
190
+ background-color: var(--lesli-color-success-50);
191
+ }
192
+ .bg-success-500 {
193
+ background-color: var(--lesli-color-success-500);
194
+ }
195
+ .bg-warning-50 {
196
+ background-color: var(--lesli-color-warning-50);
197
+ }
198
+ .bg-warning-500 {
199
+ background-color: var(--lesli-color-warning-500);
200
+ }
201
+ .bg-white {
202
+ background-color: var(--color-white);
203
+ }
204
+ .px-2 {
205
+ padding-inline: calc(var(--spacing) * 2);
206
+ }
207
+ .px-3 {
208
+ padding-inline: calc(var(--spacing) * 3);
209
+ }
210
+ .px-4 {
211
+ padding-inline: calc(var(--spacing) * 4);
212
+ }
213
+ .py-0\.5 {
214
+ padding-block: calc(var(--spacing) * 0.5);
215
+ }
216
+ .py-1\.5 {
217
+ padding-block: calc(var(--spacing) * 1.5);
218
+ }
219
+ .py-3\.5 {
220
+ padding-block: calc(var(--spacing) * 3.5);
221
+ }
222
+ .pr-14 {
223
+ padding-right: calc(var(--spacing) * 14);
224
+ }
225
+ .text-base {
226
+ font-size: var(--text-base);
227
+ line-height: var(--tw-leading, var(--text-base--line-height));
228
+ }
229
+ .text-lg {
230
+ font-size: var(--text-lg);
231
+ line-height: var(--tw-leading, var(--text-lg--line-height));
232
+ }
233
+ .text-sm {
234
+ font-size: var(--text-sm);
235
+ line-height: var(--tw-leading, var(--text-sm--line-height));
236
+ }
237
+ .text-xl {
238
+ font-size: var(--text-xl);
239
+ line-height: var(--tw-leading, var(--text-xl--line-height));
240
+ }
241
+ .text-xs {
242
+ font-size: var(--text-xs);
243
+ line-height: var(--tw-leading, var(--text-xs--line-height));
244
+ }
245
+ .leading-5 {
246
+ --tw-leading: calc(var(--spacing) * 5);
247
+ line-height: calc(var(--spacing) * 5);
248
+ }
249
+ .leading-none {
250
+ --tw-leading: 1;
251
+ line-height: 1;
252
+ }
253
+ .font-medium {
254
+ --tw-font-weight: var(--font-weight-medium);
255
+ font-weight: var(--font-weight-medium);
256
+ }
257
+ .font-semibold {
258
+ --tw-font-weight: var(--font-weight-semibold);
259
+ font-weight: var(--font-weight-semibold);
260
+ }
261
+ .text-danger-700 {
262
+ color: var(--lesli-color-danger-700);
263
+ }
264
+ .text-info-700 {
265
+ color: var(--lesli-color-info-700);
266
+ }
267
+ .text-neutral-700 {
268
+ color: var(--lesli-color-neutral-700);
269
+ }
270
+ .text-sky-700 {
271
+ color: var(--color-sky-700);
272
+ }
273
+ .text-slate-400 {
274
+ color: var(--color-slate-400);
275
+ }
276
+ .text-slate-500 {
277
+ color: var(--color-slate-500);
278
+ }
279
+ .text-slate-600 {
280
+ color: var(--color-slate-600);
281
+ }
282
+ .text-slate-800 {
283
+ color: var(--color-slate-800);
284
+ }
285
+ .text-success-700 {
286
+ color: var(--lesli-color-success-700);
287
+ }
288
+ .text-warning-700 {
289
+ color: var(--lesli-color-warning-700);
290
+ }
291
+ .shadow-sm {
292
+ --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
293
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
294
+ }
295
+ .ring-1 {
296
+ --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
297
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
298
+ }
299
+ .ring-danger-200 {
300
+ --tw-ring-color: var(--lesli-color-danger-200);
301
+ }
302
+ .ring-sky-200 {
303
+ --tw-ring-color: var(--color-sky-200);
304
+ }
305
+ .ring-warning-200 {
306
+ --tw-ring-color: var(--lesli-color-warning-200);
307
+ }
308
+ .transition-colors {
309
+ transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;
310
+ transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
311
+ transition-duration: var(--tw-duration, var(--default-transition-duration));
312
+ }
313
+ .ring-inset {
314
+ --tw-ring-inset: inset;
315
+ }
316
+ @media (hover: hover) {
317
+ .hover\:bg-sky-100:hover {
318
+ background-color: var(--color-sky-100);
319
+ }
320
+ .hover\:bg-slate-50:hover {
321
+ background-color: var(--color-slate-50);
322
+ }
323
+ .hover\:text-sky-700:hover {
324
+ color: var(--color-sky-700);
325
+ }
326
+ .hover\:text-sky-800:hover {
327
+ color: var(--color-sky-800);
328
+ }
329
+ }
330
+ .focus-visible\:ring-2:focus-visible {
331
+ --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
332
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
333
+ }
334
+ .focus-visible\:ring-sky-500\/30:focus-visible {
335
+ --tw-ring-color: color-mix(in srgb, oklch(68.5% 0.169 237.323) 30%, transparent);
336
+ @supports (color: color-mix(in lab, red, red)) {
337
+ --tw-ring-color: color-mix(in oklab, var(--color-sky-500) 30%, transparent);
338
+ }
339
+ }
340
+ .focus-visible\:ring-offset-2:focus-visible {
341
+ --tw-ring-offset-width: 2px;
342
+ --tw-ring-offset-shadow: var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
343
+ }
344
+ .focus-visible\:outline-none:focus-visible {
345
+ --tw-outline-style: none;
346
+ outline-style: none;
347
+ }
348
+ @media (width >= 40rem) {
349
+ .sm\:top-4 {
350
+ top: calc(var(--spacing) * 4);
351
+ }
352
+ .sm\:right-5 {
353
+ right: calc(var(--spacing) * 5);
354
+ }
355
+ .sm\:px-5 {
356
+ padding-inline: calc(var(--spacing) * 5);
357
+ }
358
+ .sm\:py-4 {
359
+ padding-block: calc(var(--spacing) * 4);
360
+ }
361
+ .sm\:pr-16 {
362
+ padding-right: calc(var(--spacing) * 16);
363
+ }
364
+ .sm\:text-base {
365
+ font-size: var(--text-base);
366
+ line-height: var(--tw-leading, var(--text-base--line-height));
367
+ }
368
+ }
369
+ }
370
+ @property --tw-divide-y-reverse {
371
+ syntax: "*";
372
+ inherits: false;
373
+ initial-value: 0;
374
+ }
375
+ @property --tw-border-style {
376
+ syntax: "*";
377
+ inherits: false;
378
+ initial-value: solid;
379
+ }
380
+ @property --tw-leading {
381
+ syntax: "*";
382
+ inherits: false;
383
+ }
384
+ @property --tw-font-weight {
385
+ syntax: "*";
386
+ inherits: false;
387
+ }
388
+ @property --tw-shadow {
389
+ syntax: "*";
390
+ inherits: false;
391
+ initial-value: 0 0 #0000;
392
+ }
393
+ @property --tw-shadow-color {
394
+ syntax: "*";
395
+ inherits: false;
396
+ }
397
+ @property --tw-shadow-alpha {
398
+ syntax: "<percentage>";
399
+ inherits: false;
400
+ initial-value: 100%;
401
+ }
402
+ @property --tw-inset-shadow {
403
+ syntax: "*";
404
+ inherits: false;
405
+ initial-value: 0 0 #0000;
406
+ }
407
+ @property --tw-inset-shadow-color {
408
+ syntax: "*";
409
+ inherits: false;
410
+ }
411
+ @property --tw-inset-shadow-alpha {
412
+ syntax: "<percentage>";
413
+ inherits: false;
414
+ initial-value: 100%;
415
+ }
416
+ @property --tw-ring-color {
417
+ syntax: "*";
418
+ inherits: false;
419
+ }
420
+ @property --tw-ring-shadow {
421
+ syntax: "*";
422
+ inherits: false;
423
+ initial-value: 0 0 #0000;
424
+ }
425
+ @property --tw-inset-ring-color {
426
+ syntax: "*";
427
+ inherits: false;
428
+ }
429
+ @property --tw-inset-ring-shadow {
430
+ syntax: "*";
431
+ inherits: false;
432
+ initial-value: 0 0 #0000;
433
+ }
434
+ @property --tw-ring-inset {
435
+ syntax: "*";
436
+ inherits: false;
437
+ }
438
+ @property --tw-ring-offset-width {
439
+ syntax: "<length>";
440
+ inherits: false;
441
+ initial-value: 0px;
442
+ }
443
+ @property --tw-ring-offset-color {
444
+ syntax: "*";
445
+ inherits: false;
446
+ initial-value: #fff;
447
+ }
448
+ @property --tw-ring-offset-shadow {
449
+ syntax: "*";
450
+ inherits: false;
451
+ initial-value: 0 0 #0000;
452
+ }
453
+ @layer properties {
454
+ @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
455
+ *, ::before, ::after, ::backdrop {
456
+ --tw-divide-y-reverse: 0;
457
+ --tw-border-style: solid;
458
+ --tw-leading: initial;
459
+ --tw-font-weight: initial;
460
+ --tw-shadow: 0 0 #0000;
461
+ --tw-shadow-color: initial;
462
+ --tw-shadow-alpha: 100%;
463
+ --tw-inset-shadow: 0 0 #0000;
464
+ --tw-inset-shadow-color: initial;
465
+ --tw-inset-shadow-alpha: 100%;
466
+ --tw-ring-color: initial;
467
+ --tw-ring-shadow: 0 0 #0000;
468
+ --tw-inset-ring-color: initial;
469
+ --tw-inset-ring-shadow: 0 0 #0000;
470
+ --tw-ring-inset: initial;
471
+ --tw-ring-offset-width: 0px;
472
+ --tw-ring-offset-color: #fff;
473
+ --tw-ring-offset-shadow: 0 0 #0000;
474
+ }
475
+ }
476
+ }
@@ -37,6 +37,12 @@ module LesliBell
37
37
  render :index
38
38
  end
39
39
 
40
+ # PUT /notifications/mark_all_as_read
41
+ def mark_all_as_read
42
+ NotificationService.new(current_user).read_all
43
+ redirect_to notifications_path, status: :see_other
44
+ end
45
+
40
46
  # DELETE /notifications/1
41
47
  def destroy
42
48
  @notification.destroy
@@ -1,6 +1,9 @@
1
1
  module LesliBell
2
2
  class Account < ApplicationRecord
3
- belongs_to :account, class_name: "::Lesli::Account"
3
+ belongs_to :account,
4
+ class_name: "Lesli::Account",
5
+ inverse_of: :bell
6
+ validates :account_id, uniqueness: true
4
7
  has_many :notifications
5
8
  has_many :announcements
6
9
  end
@@ -78,5 +78,13 @@ module LesliBell
78
78
  noti.update(:status => :read)
79
79
  self
80
80
  end
81
+
82
+ def read_all
83
+ current_user.account.bell.notifications
84
+ .where(user: current_user, status: ["created", "sent", nil])
85
+ .update_all(status: "read", updated_at: Time.current)
86
+
87
+ self
88
+ end
81
89
  end
82
90
  end
@@ -30,4 +30,4 @@ Building a better future, one line of code at a time.
30
30
  // ·
31
31
  %>
32
32
 
33
- <%= render("/lesli/shared/dashboards/show") %>
33
+ <%= render(template:"lesli/shared/dashboards/show") %>
@@ -2,7 +2,7 @@
2
2
 
3
3
  Lesli
4
4
 
5
- Copyright (c) 2023, 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
@@ -32,82 +32,146 @@ Building a better future, one line of code at a time.
32
32
 
33
33
  <%= render(LesliView::Layout::Container.new("lesli-bell-notifications-index")) do %>
34
34
 
35
- <%= render(LesliView::Components::Header.new("Notifications")) do %>
36
- <%= render(LesliView::Elements::Button.new("mark all as read", icon:"check", solid: true)) %>
35
+ <%= render(LesliView::Components::Header.new(
36
+ "Notifications",
37
+ "Review important updates and mark them as read when you are done."
38
+ )) do %>
39
+ <span class="inline-flex w-fit items-center gap-2 rounded-full bg-sky-50 px-3 py-1.5 text-sm font-medium text-sky-700 ring-1 ring-inset ring-sky-200">
40
+ <span class="material-symbols text-lg leading-none" aria-hidden="true">notifications</span>
41
+ <%= pluralize(@notifications.dig(:pagination, :total) || @notifications[:records].size, "unread notification") %>
42
+ </span>
43
+ <%= render(LesliView::Elements::Button.new(
44
+ "Mark all as read",
45
+ url: mark_all_as_read_notifications_path,
46
+ icon: "done_all",
47
+ method: :put,
48
+ disabled: @notifications[:records].empty?,
49
+ data: { turbo_submits_with: "Marking all as read..." }
50
+ )) %>
37
51
  <% end %>
38
52
 
39
- <%= render(LesliView::Components::Toolbar.new) %>
40
-
41
53
  <% if @notifications[:records].empty? %>
42
- <%= render(LesliView::Elements::Empty.new) %>
54
+ <div class="overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm">
55
+ <%= render(LesliView::Elements::Empty.new(
56
+ text: "You're all caught up",
57
+ description: "New unread notifications will appear here."
58
+ )) %>
59
+ </div>
43
60
  <% else %>
44
- <section class="space-y-2" aria-label="Notifications">
45
- <% @notifications[:records].each do |notification| %>
46
- <%
47
- notification_classes, close_button_classes = case notification[:category].to_s
48
- when "danger"
49
- [
50
- "border-danger-300 bg-danger-50 text-danger-900",
51
- "text-danger-700 hover:bg-danger-100 focus-visible:ring-danger-500/40"
52
- ]
53
- when "warning"
54
- [
55
- "border-warning-300 bg-warning-50 text-warning-900",
56
- "text-warning-700 hover:bg-warning-100 focus-visible:ring-warning-500/40"
57
- ]
58
- when "success"
59
- [
60
- "border-success-300 bg-success-50 text-success-900",
61
- "text-success-700 hover:bg-success-100 focus-visible:ring-success-500/40"
62
- ]
63
- when "info"
64
- [
65
- "border-info-300 bg-info-50 text-info-900",
66
- "text-info-700 hover:bg-info-100 focus-visible:ring-info-500/40"
67
- ]
68
- else
69
- [
70
- "border-neutral-300 bg-neutral-50 text-neutral-900",
71
- "text-neutral-700 hover:bg-neutral-100 focus-visible:ring-neutral-500/40"
72
- ]
73
- end
74
- %>
75
-
76
- <article class="relative rounded-lg border-2 px-4 py-3 pr-14 shadow-sm <%= notification_classes %>">
77
-
78
- <!-- Close button -->
79
- <%= button_to(
80
- notification_path(notification[:id]),
81
- method: :put,
82
- class: "inline-flex size-8 items-center justify-center rounded-md transition-colors focus-visible:outline-none focus-visible:ring-2 #{close_button_classes}",
83
- form: { class: "absolute right-3 top-3" },
84
- aria: { label: "Mark notification as read" }) do %>
85
- <span class="material-symbols text-xl" aria-hidden="true">close</span>
86
- <% end %>
87
-
88
- <!-- Notification subject -->
89
- <strong class="block text-sm font-semibold"><%= notification[:subject] %></strong>
90
-
91
- <!-- Notification body (optional) -->
92
- <% if notification[:body].present? %>
93
- <p class="mt-1 text-sm leading-6"><%= notification[:body] %></p>
94
- <% end %>
95
-
96
- <!-- Notification URL (optional) -->
97
- <% if notification[:url].present? %>
98
- <p class="mt-2 text-sm">
99
- <a
100
- class="break-all font-medium underline decoration-current/40 underline-offset-2 hover:decoration-current focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-current/40"
101
- href="<%= notification[:url] %>"
102
- target="_blank"
103
- rel="noopener noreferrer"
104
- >
105
- <%= notification[:url] %>
106
- </a>
107
- </p>
61
+ <section aria-label="Unread notifications">
62
+ <div class="overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm">
63
+ <ol class="divide-y divide-slate-200">
64
+ <% @notifications[:records].each do |notification| %>
65
+ <%
66
+ category_label, category_icon, accent_classes, icon_classes, badge_classes = case notification[:category].to_s
67
+ when "danger"
68
+ [
69
+ "Attention",
70
+ "error",
71
+ "bg-danger-500",
72
+ "bg-danger-50 text-danger-700",
73
+ "bg-danger-50 text-danger-700 ring-danger-200"
74
+ ]
75
+ when "warning"
76
+ [
77
+ "Warning",
78
+ "warning",
79
+ "bg-warning-500",
80
+ "bg-warning-50 text-warning-700",
81
+ "bg-warning-50 text-warning-700 ring-warning-200"
82
+ ]
83
+ when "success"
84
+ [
85
+ nil,
86
+ "check_circle",
87
+ "bg-success-500",
88
+ "bg-success-50 text-success-700",
89
+ nil
90
+ ]
91
+ when "info"
92
+ [
93
+ nil,
94
+ "info",
95
+ "bg-info-500",
96
+ "bg-info-50 text-info-700",
97
+ nil
98
+ ]
99
+ else
100
+ [
101
+ nil,
102
+ "notifications",
103
+ "bg-neutral-400",
104
+ "bg-neutral-50 text-neutral-700",
105
+ nil
106
+ ]
107
+ end
108
+ %>
109
+
110
+ <li>
111
+ <article class="group relative bg-white px-4 py-3.5 pr-14 transition-colors hover:bg-slate-50 sm:px-5 sm:py-4 sm:pr-16">
112
+ <span class="absolute inset-y-0 left-0 w-1 <%= accent_classes %>" aria-hidden="true"></span>
113
+
114
+ <%= button_to(
115
+ notification_path(notification[:id]),
116
+ method: :put,
117
+ class: "inline-flex size-8 shrink-0 items-center justify-center rounded-full border border-transparent bg-slate-100 text-slate-500 transition-colors hover:bg-sky-100 hover:text-sky-800 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-500/30 focus-visible:ring-offset-2",
118
+ form: { class: "absolute right-4 top-3.5 inline-flex sm:right-5 sm:top-4" },
119
+ data: { turbo_submits_with: "…" },
120
+ title: "Mark as read",
121
+ aria: { label: "Mark #{notification[:subject]} as read" }
122
+ ) do %>
123
+ <span class="material-symbols text-lg leading-none" aria-hidden="true">done</span>
124
+ <% end %>
125
+
126
+ <div class="flex items-start gap-3">
127
+ <div class="flex size-9 shrink-0 items-center justify-center rounded-lg <%= icon_classes %>">
128
+ <span class="material-symbols text-xl leading-none" aria-hidden="true"><%= category_icon %></span>
129
+ </div>
130
+
131
+ <div class="min-w-0 flex-1">
132
+ <div class="flex flex-wrap items-center gap-x-2 gap-y-1">
133
+ <h4 class="text-sm font-semibold leading-5 text-slate-800 sm:text-base">
134
+ <% if notification[:url].present? %>
135
+ <a
136
+ class="inline-flex items-center gap-1 rounded-md transition-colors hover:text-sky-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-500/30 focus-visible:ring-offset-2"
137
+ href="<%= notification[:url] %>"
138
+ target="_blank"
139
+ rel="noopener noreferrer">
140
+ <%= notification[:subject] %>
141
+ <span class="material-symbols text-base leading-none text-slate-400" aria-hidden="true">open_in_new</span>
142
+ </a>
143
+ <% else %>
144
+ <%= notification[:subject] %>
145
+ <% end %>
146
+ </h4>
147
+
148
+ <% if category_label.present? %>
149
+ <span class="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-semibold ring-1 ring-inset <%= badge_classes %>">
150
+ <%= category_label %>
151
+ </span>
152
+ <% end %>
153
+ </div>
154
+
155
+ <% if notification[:body].present? %>
156
+ <p class="mt-1 text-sm leading-5 text-slate-600 <%= "line-clamp-2" if notification[:url].present? %>">
157
+ <%= notification[:body] %>
158
+ </p>
159
+ <% end %>
160
+
161
+ <% if notification[:created_at].present? %>
162
+ <time
163
+ class="mt-1.5 block text-xs text-slate-400"
164
+ datetime="<%= notification[:created_at].iso8601 %>">
165
+ <%= time_ago_in_words(notification[:created_at]) %> ago
166
+ </time>
167
+ <% end %>
168
+ </div>
169
+ </div>
170
+ </article>
171
+ </li>
108
172
  <% end %>
109
- </article>
110
- <% end %>
173
+ </ol>
174
+ </div>
111
175
  </section>
112
176
  <% end %>
113
177
  <% end %>
data/config/routes.rb CHANGED
@@ -32,6 +32,8 @@ Building a better future, one line of code at a time.
32
32
 
33
33
  LesliBell::Engine.routes.draw do
34
34
  Lesli::Router.mount_lesli_engine_routes(self)
35
- resources :notifications, only: [:index, :new, :update]
35
+ resources :notifications, only: [:index, :new, :update] do
36
+ put :mark_all_as_read, on: :collection
37
+ end
36
38
  resources :announcements, only: [:index]
37
39
  end
@@ -47,6 +47,11 @@ class CreateLesliBellNotifications < ActiveRecord::Migration[7.0]
47
47
  end
48
48
 
49
49
  add_reference(:lesli_bell_notifications, :user, foreign_key: { to_table: :lesli_users })
50
- add_reference(:lesli_bell_notifications, :account, foreign_key: { to_table: :lesli_accounts })
50
+ add_reference(
51
+ :lesli_bell_notifications,
52
+ :account,
53
+ null: false,
54
+ foreign_key: { to_table: :lesli_bell_accounts }
55
+ )
51
56
  end
52
57
  end
@@ -49,6 +49,6 @@ class CreateLesliBellAnnouncements < ActiveRecord::Migration[6.0]
49
49
 
50
50
  add_reference(:lesli_bell_announcements, :user, foreign_key: { to_table: :lesli_users })
51
51
  add_reference(:lesli_bell_announcements, :role, foreign_key: { to_table: :lesli_roles })
52
- add_reference(:lesli_bell_announcements, :account, foreign_key: { to_table: :lesli_accounts })
52
+ add_reference(:lesli_bell_announcements, :account, null: false, foreign_key: { to_table: :lesli_bell_accounts })
53
53
  end
54
54
  end
@@ -1,4 +1,4 @@
1
1
  module LesliBell
2
- VERSION = "1.1.1"
3
- BUILD = "1791607001"
2
+ VERSION = "1.1.2"
3
+ BUILD = "1792716699"
4
4
  end
data/readme.md CHANGED
@@ -8,10 +8,10 @@
8
8
  <br />
9
9
 
10
10
  <div align="center">
11
- <a target="_blank" href="https://github.com/LesliTech/LesliBell/actions/workflows/lesli-ci-tests.yaml">
11
+ <a target="_blank" href="https://github.com/LesliTech/LesliBell/actions/workflows/lesli-ci-engine.yaml">
12
12
  <img
13
13
  alt="LesliBell test status"
14
- src="https://img.shields.io/github/actions/workflow/status/LesliTech/LesliBell/lesli-ci-tests.yaml?branch=master&style=for-the-badge&logo=github&label=tests">
14
+ src="https://img.shields.io/github/actions/workflow/status/LesliTech/LesliBell/lesli-ci-engine.yaml?branch=master&style=for-the-badge&logo=github&label=tests">
15
15
  </a>
16
16
  <a target="_blank" href="https://rubygems.org/gems/lesli_bell">
17
17
  <img alt="Gem Version" src="https://img.shields.io/gem/v/lesli_bell?style=for-the-badge&logo=ruby">
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lesli_bell
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - The Lesli Development Team
8
+ autorequire:
8
9
  bindir: bin
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2026-10-23 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: lesli
@@ -31,12 +32,10 @@ extensions: []
31
32
  extra_rdoc_files: []
32
33
  files:
33
34
  - Rakefile
34
- - app/assets/config/lesli_bell_manifest.js
35
35
  - app/assets/images/lesli_bell/bell-logo.svg
36
36
  - app/assets/javascripts/lesli_bell/application.js
37
37
  - app/assets/javascripts/lesli_bell/application.js.LICENSE.txt
38
38
  - app/assets/stylesheets/lesli_bell/application.css
39
- - app/assets/stylesheets/lesli_bell/application.tailwind.css
40
39
  - app/controllers/lesli_bell/accounts_controller.rb
41
40
  - app/controllers/lesli_bell/announcements_controller.rb
42
41
  - app/controllers/lesli_bell/application_controller.rb
@@ -102,6 +101,7 @@ metadata:
102
101
  homepage_uri: https://www.lesli.dev/
103
102
  changelog_uri: https://github.com/LesliTech/LesliBell
104
103
  source_code_uri: https://github.com/LesliTech/LesliBell
104
+ post_install_message:
105
105
  rdoc_options: []
106
106
  require_paths:
107
107
  - lib
@@ -116,7 +116,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
116
116
  - !ruby/object:Gem::Version
117
117
  version: '0'
118
118
  requirements: []
119
- rubygems_version: 3.7.1
119
+ rubygems_version: 3.5.16
120
+ signing_key:
120
121
  specification_version: 4
121
122
  summary: Notifications & Announcements System for the Lesli Framework.
122
123
  test_files: []
@@ -1,40 +0,0 @@
1
- /*
2
- Lesli
3
-
4
- Copyright (c) 2023, Lesli Technologies, S. A.
5
-
6
- This program is free software: you can redistribute it and/or modify
7
- it under the terms of the GNU General Public License as published by
8
- the Free Software Foundation, either version 3 of the License, or
9
- (at your option) any later version.
10
-
11
- This program is distributed in the hope that it will be useful,
12
- but WITHOUT ANY WARRANTY; without even the implied warranty of
13
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
- GNU General Public License for more details.
15
-
16
- You should have received a copy of the GNU General Public License
17
- along with this program. If not, see http://www.gnu.org/licenses/.
18
-
19
- Lesli · Ruby on Rails SaaS Development Framework.
20
-
21
- Made with ♥ by https://www.lesli.tech
22
- Building a better future, one line of code at a time.
23
-
24
- @contact hello@lesli.tech
25
- @website https://www.lesli.dev
26
- @license GPLv3 http://www.gnu.org/licenses/gpl-3.0.en.html
27
-
28
- // · ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~
29
- // ·
30
- */
31
-
32
-
33
- // · Engine asssets
34
- // · ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~
35
- //= link_tree ../images/lesli_bell .png
36
- //= link_tree ../images/lesli_bell .jpg
37
- //= link_tree ../images/lesli_bell .svg
38
-
39
- //= link lesli_bell/application.js
40
- //= link lesli_bell/application.css
@@ -1,2 +0,0 @@
1
- /*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
2
- @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000}}}@layer theme{:root,:host{--spacing:.25rem;--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--font-weight-medium:500;--font-weight-semibold:600;--radius-md:.375rem;--radius-lg:.5rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1)}}@layer utilities{.absolute{position:absolute}.relative{position:relative}.top-3{top:calc(var(--spacing) * 3)}.right-3{right:calc(var(--spacing) * 3)}.mt-1{margin-top:var(--spacing)}.mt-2{margin-top:calc(var(--spacing) * 2)}.block{display:block}.inline-flex{display:inline-flex}.size-8{width:calc(var(--spacing) * 8);height:calc(var(--spacing) * 8)}.items-center{align-items:center}.justify-center{justify-content:center}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-danger-300{border-color:var(--lesli-color-danger-300)}.border-info-300{border-color:var(--lesli-color-info-300)}.border-neutral-300{border-color:var(--lesli-color-neutral-300)}.border-success-300{border-color:var(--lesli-color-success-300)}.border-warning-300{border-color:var(--lesli-color-warning-300)}.bg-danger-50{background-color:var(--lesli-color-danger-50)}.bg-info-50{background-color:var(--lesli-color-info-50)}.bg-neutral-50{background-color:var(--lesli-color-neutral-50)}.bg-success-50{background-color:var(--lesli-color-success-50)}.bg-warning-50{background-color:var(--lesli-color-warning-50)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-3{padding-block:calc(var(--spacing) * 3)}.pr-14{padding-right:calc(var(--spacing) * 14)}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.leading-6{--tw-leading:calc(var(--spacing) * 6);line-height:calc(var(--spacing) * 6)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.break-all{word-break:break-all}.text-danger-700{color:var(--lesli-color-danger-700)}.text-danger-900{color:var(--lesli-color-danger-900)}.text-info-700{color:var(--lesli-color-info-700)}.text-info-900{color:var(--lesli-color-info-900)}.text-neutral-700{color:var(--lesli-color-neutral-700)}.text-neutral-900{color:var(--lesli-color-neutral-900)}.text-success-700{color:var(--lesli-color-success-700)}.text-success-900{color:var(--lesli-color-success-900)}.text-warning-700{color:var(--lesli-color-warning-700)}.text-warning-900{color:var(--lesli-color-warning-900)}.underline{text-decoration-line:underline}.decoration-current\/40{text-decoration-color:currentColor}@supports (color:color-mix(in lab, red, red)){.decoration-current\/40{-webkit-text-decoration-color:color-mix(in oklab, currentcolor 40%, transparent);-webkit-text-decoration-color:color-mix(in oklab, currentcolor 40%, transparent);text-decoration-color:color-mix(in oklab, currentcolor 40%, transparent)}}.underline-offset-2{text-underline-offset:2px}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media (hover:hover){.hover\:bg-danger-100:hover{background-color:var(--lesli-color-danger-100)}.hover\:bg-info-100:hover{background-color:var(--lesli-color-info-100)}.hover\:bg-neutral-100:hover{background-color:var(--lesli-color-neutral-100)}.hover\:bg-success-100:hover{background-color:var(--lesli-color-success-100)}.hover\:bg-warning-100:hover{background-color:var(--lesli-color-warning-100)}.hover\:decoration-current:hover{text-decoration-color:currentColor}}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus-visible\:ring-current\/40:focus-visible{--tw-ring-color:currentcolor}@supports (color:color-mix(in lab, red, red)){.focus-visible\:ring-current\/40:focus-visible{--tw-ring-color:color-mix(in oklab, currentcolor 40%, transparent)}}.focus-visible\:ring-danger-500\/40:focus-visible{--tw-ring-color:var(--lesli-color-danger-500)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:ring-danger-500\/40:focus-visible{--tw-ring-color:color-mix(in oklab, var(--lesli-color-danger-500) 40%, transparent)}}.focus-visible\:ring-info-500\/40:focus-visible{--tw-ring-color:var(--lesli-color-info-500)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:ring-info-500\/40:focus-visible{--tw-ring-color:color-mix(in oklab, var(--lesli-color-info-500) 40%, transparent)}}.focus-visible\:ring-neutral-500\/40:focus-visible{--tw-ring-color:var(--lesli-color-neutral-500)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:ring-neutral-500\/40:focus-visible{--tw-ring-color:color-mix(in oklab, var(--lesli-color-neutral-500) 40%, transparent)}}.focus-visible\:ring-success-500\/40:focus-visible{--tw-ring-color:var(--lesli-color-success-500)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:ring-success-500\/40:focus-visible{--tw-ring-color:color-mix(in oklab, var(--lesli-color-success-500) 40%, transparent)}}.focus-visible\:ring-warning-500\/40:focus-visible{--tw-ring-color:var(--lesli-color-warning-500)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:ring-warning-500\/40:focus-visible{--tw-ring-color:color-mix(in oklab, var(--lesli-color-warning-500) 40%, transparent)}}.focus-visible\:outline-none:focus-visible{--tw-outline-style:none;outline-style:none}}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}