sincerely 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -0
  3. data/app/assets/config/sincerely.js +2 -0
  4. data/app/assets/images/sincerely/arrow_left.svg +1 -0
  5. data/app/assets/images/sincerely/check.svg +3 -0
  6. data/app/assets/images/sincerely/chevron_left.svg +1 -0
  7. data/app/assets/images/sincerely/clock.svg +1 -0
  8. data/app/assets/images/sincerely/code.svg +1 -0
  9. data/app/assets/images/sincerely/eye.svg +1 -0
  10. data/app/assets/images/sincerely/logout.svg +1 -0
  11. data/app/assets/images/sincerely/menu.svg +1 -0
  12. data/app/assets/images/sincerely/moon.svg +1 -0
  13. data/app/assets/images/sincerely/pencil.svg +4 -0
  14. data/app/assets/images/sincerely/plus.svg +3 -0
  15. data/app/assets/images/sincerely/progress_spinner.svg +3 -0
  16. data/app/assets/images/sincerely/spinner.svg +3 -0
  17. data/app/assets/images/sincerely/sun.svg +1 -0
  18. data/app/assets/images/sincerely/warning_circle.svg +4 -0
  19. data/app/assets/javascripts/sincerely/charts.js +115 -0
  20. data/app/assets/javascripts/sincerely/clickable_rows.js +29 -0
  21. data/app/assets/javascripts/sincerely/layout.js +104 -0
  22. data/app/assets/javascripts/sincerely/send_form.js +243 -0
  23. data/app/assets/javascripts/sincerely/template_form.js +44 -0
  24. data/app/assets/javascripts/sincerely/template_preview.js +12 -0
  25. data/app/assets/javascripts/sincerely/theme_init.js +9 -0
  26. data/app/assets/stylesheets/sincerely/charts.css +250 -0
  27. data/app/assets/stylesheets/sincerely/components.css +632 -0
  28. data/app/assets/stylesheets/sincerely/layout.css +233 -0
  29. data/app/assets/stylesheets/sincerely/navigation.css +198 -0
  30. data/app/assets/stylesheets/sincerely/send_form.css +287 -0
  31. data/app/assets/stylesheets/sincerely/template_form.css +74 -0
  32. data/app/assets/stylesheets/sincerely/template_preview.css +75 -0
  33. data/app/assets/stylesheets/sincerely/variables.css +88 -0
  34. data/app/controllers/sincerely/application_controller.rb +3 -72
  35. data/app/controllers/sincerely/concerns/authorization.rb +21 -0
  36. data/app/controllers/sincerely/concerns/delivery_metrics.rb +51 -0
  37. data/app/controllers/sincerely/concerns/event_filtering.rb +58 -0
  38. data/app/controllers/sincerely/concerns/event_timeline.rb +41 -0
  39. data/app/controllers/sincerely/concerns/notification_timeline.rb +73 -0
  40. data/app/controllers/sincerely/concerns/notifications.rb +47 -0
  41. data/app/controllers/sincerely/concerns/pagination.rb +39 -0
  42. data/app/controllers/sincerely/concerns/periods.rb +40 -0
  43. data/app/controllers/sincerely/dashboard_controller.rb +30 -88
  44. data/app/controllers/sincerely/delivery_events_controller.rb +13 -21
  45. data/app/controllers/sincerely/engagement_events_controller.rb +13 -22
  46. data/app/controllers/sincerely/notifications_controller.rb +68 -49
  47. data/app/controllers/sincerely/send_controller.rb +82 -67
  48. data/app/controllers/sincerely/templates_controller.rb +37 -8
  49. data/app/helpers/sincerely/application_helper.rb +22 -0
  50. data/app/helpers/sincerely/chart_helper.rb +79 -0
  51. data/app/views/layouts/sincerely/application.html.erb +24 -528
  52. data/app/views/sincerely/dashboard/index.html.erb +25 -256
  53. data/app/views/sincerely/delivery_events/index.html.erb +6 -38
  54. data/app/views/sincerely/engagement_events/index.html.erb +6 -38
  55. data/app/views/sincerely/notifications/index.html.erb +7 -23
  56. data/app/views/sincerely/notifications/show.html.erb +4 -4
  57. data/app/views/sincerely/send/new.html.erb +8 -522
  58. data/app/views/sincerely/shared/_event.html.erb +21 -0
  59. data/app/views/sincerely/shared/_line_chart.html.erb +38 -0
  60. data/app/views/sincerely/shared/_notification_type_filter.html.erb +9 -0
  61. data/app/views/sincerely/shared/_status_option.html.erb +1 -0
  62. data/app/views/sincerely/shared/_template_option.html.erb +3 -0
  63. data/app/views/sincerely/templates/_form.html.erb +8 -107
  64. data/app/views/sincerely/templates/edit.html.erb +2 -2
  65. data/app/views/sincerely/templates/index.html.erb +7 -15
  66. data/app/views/sincerely/templates/new.html.erb +2 -2
  67. data/app/views/sincerely/templates/preview.html.erb +7 -26
  68. data/app/views/sincerely/templates/show.html.erb +3 -3
  69. data/lib/config/sincerely_config.rb +2 -2
  70. data/lib/sincerely/delivery_systems/email_aws_ses.rb +2 -2
  71. data/lib/sincerely/engine.rb +4 -0
  72. data/lib/sincerely/mixins/notification_model.rb +5 -1
  73. data/lib/sincerely/services/process_delivery_event.rb +9 -7
  74. data/lib/sincerely/templates/notification_template.rb +1 -1
  75. data/lib/sincerely/version.rb +1 -1
  76. metadata +52 -4
  77. data/sincerely.gemspec +0 -37
@@ -0,0 +1,632 @@
1
+ /* Cards */
2
+ .card {
3
+ background: var(--bg-primary);
4
+ border-radius: 0.5rem;
5
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
6
+ padding: 1.5rem;
7
+ margin-bottom: 1.5rem;
8
+ transition: background-color 0.3s;
9
+ overflow: hidden;
10
+ min-width: 0;
11
+ }
12
+
13
+ .card-header {
14
+ display: flex;
15
+ justify-content: space-between;
16
+ align-items: center;
17
+ margin-bottom: 1rem;
18
+ padding-bottom: 1rem;
19
+ border-bottom: 1px solid var(--border-color);
20
+ }
21
+
22
+ .card-title {
23
+ font-family: 'Jersey 10', cursive;
24
+ font-weight: 400;
25
+ font-size: 1.5rem;
26
+ }
27
+
28
+ /* Stats */
29
+ .stats-grid {
30
+ display: grid;
31
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
32
+ gap: 1rem;
33
+ margin-bottom: 2rem;
34
+ }
35
+
36
+ .stat-card {
37
+ background: var(--bg-primary);
38
+ border-radius: 0.5rem;
39
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
40
+ padding: 1.5rem;
41
+ transition: background-color 0.3s;
42
+ min-width: 0;
43
+ overflow: hidden;
44
+ }
45
+
46
+ .stat-label {
47
+ color: var(--gray-500);
48
+ font-size: 0.875rem;
49
+ }
50
+
51
+ .stat-value {
52
+ font-size: 2rem;
53
+ font-weight: 700;
54
+ margin-top: 0.25rem;
55
+ }
56
+
57
+ .stat-value.primary {
58
+ color: var(--success);
59
+ }
60
+
61
+ .stat-value.success {
62
+ color: var(--success);
63
+ }
64
+
65
+ .stat-value.warning {
66
+ color: var(--warning);
67
+ }
68
+
69
+ .stat-value.danger {
70
+ color: var(--danger);
71
+ }
72
+
73
+ /* Stats Mini */
74
+ .stats-mini {
75
+ display: flex;
76
+ gap: 2rem;
77
+ padding: 1rem 0;
78
+ }
79
+
80
+ .stat-mini {
81
+ text-align: center;
82
+ }
83
+
84
+ .stat-mini-value {
85
+ font-size: 2rem;
86
+ font-weight: 700;
87
+ color: var(--primary);
88
+ }
89
+
90
+ .stat-mini-label {
91
+ font-size: 0.75rem;
92
+ color: var(--gray-500);
93
+ margin-top: 0.25rem;
94
+ }
95
+
96
+ /* Tables */
97
+ .table-wrapper {
98
+ overflow-x: auto;
99
+ -webkit-overflow-scrolling: touch;
100
+ }
101
+
102
+ table {
103
+ width: 100%;
104
+ border-collapse: collapse;
105
+ }
106
+
107
+ th,
108
+ td {
109
+ text-align: left;
110
+ padding: 0.75rem 1rem;
111
+ border-bottom: 1px solid var(--border-color);
112
+ overflow: hidden;
113
+ text-overflow: ellipsis;
114
+ white-space: nowrap;
115
+ }
116
+
117
+ th {
118
+ background: var(--gray-100);
119
+ font-weight: 600;
120
+ font-size: 0.875rem;
121
+ color: var(--text-secondary);
122
+ transition: background-color 0.3s;
123
+ }
124
+
125
+ tr:hover {
126
+ background: var(--gray-100);
127
+ }
128
+
129
+ tr.clickable-row {
130
+ cursor: pointer;
131
+ transition: background-color 0.15s;
132
+ }
133
+
134
+ tr.clickable-row:hover {
135
+ background: var(--gray-200);
136
+ }
137
+
138
+ tr.clickable-row td a {
139
+ color: inherit;
140
+ text-decoration: none;
141
+ }
142
+
143
+ /* Badges */
144
+ .badge {
145
+ display: inline-block;
146
+ padding: 0.25rem 0.75rem;
147
+ border-radius: 9999px;
148
+ font-size: 0.75rem;
149
+ font-weight: 500;
150
+ }
151
+
152
+ .badge-draft {
153
+ background: var(--gray-100);
154
+ color: var(--gray-700);
155
+ }
156
+
157
+ .badge-accepted {
158
+ background: var(--info-bg);
159
+ color: var(--info-text);
160
+ }
161
+
162
+ .badge-delivered {
163
+ background: var(--success-bg);
164
+ color: var(--success-text);
165
+ }
166
+
167
+ .badge-opened {
168
+ background: var(--warning-bg);
169
+ color: var(--warning-text);
170
+ }
171
+
172
+ .badge-clicked {
173
+ background: var(--purple-bg);
174
+ color: var(--purple-text);
175
+ }
176
+
177
+ .badge-bounced,
178
+ .badge-rejected,
179
+ .badge-complained {
180
+ background: var(--danger-bg);
181
+ color: var(--danger-text);
182
+ }
183
+
184
+ .badge-delayed {
185
+ background: var(--warning-bg);
186
+ color: var(--warning-text);
187
+ }
188
+
189
+ /* Buttons */
190
+ .btn {
191
+ display: inline-flex;
192
+ align-items: center;
193
+ gap: 0.5rem;
194
+ padding: 0.5rem 1rem;
195
+ border-radius: 0.375rem;
196
+ font-weight: 500;
197
+ font-size: 0.875rem;
198
+ text-decoration: none;
199
+ cursor: pointer;
200
+ border: none;
201
+ transition: background-color 0.2s;
202
+ }
203
+
204
+ .btn-primary {
205
+ background: var(--primary);
206
+ color: white;
207
+ }
208
+
209
+ .btn-primary:hover {
210
+ background: var(--primary-dark);
211
+ }
212
+
213
+ .btn-secondary {
214
+ background: var(--bg-primary);
215
+ color: var(--text-secondary);
216
+ border: 1px solid var(--border-color);
217
+ }
218
+
219
+ .btn-secondary:hover {
220
+ background: var(--gray-100);
221
+ }
222
+
223
+ .btn-sm {
224
+ padding: 0.375rem 0.75rem;
225
+ font-size: 0.75rem;
226
+ height: 2rem;
227
+ }
228
+
229
+ .btn-icon {
230
+ display: inline-flex;
231
+ align-items: center;
232
+ justify-content: center;
233
+ width: 2rem;
234
+ height: 2rem;
235
+ border-radius: 0.375rem;
236
+ color: var(--text-secondary);
237
+ transition: all 0.15s;
238
+ }
239
+
240
+ .btn-icon:hover {
241
+ background: var(--gray-100);
242
+ color: var(--primary);
243
+ }
244
+
245
+ .actions-cell {
246
+ display: flex;
247
+ gap: 0.25rem;
248
+ }
249
+
250
+ /* Forms */
251
+ .form-group {
252
+ margin-bottom: 1rem;
253
+ }
254
+
255
+ .form-label {
256
+ display: block;
257
+ font-weight: 500;
258
+ margin-bottom: 0.5rem;
259
+ font-size: 0.875rem;
260
+ }
261
+
262
+ .form-input,
263
+ .form-select,
264
+ .form-textarea {
265
+ width: 100%;
266
+ padding: 0.5rem 0.75rem;
267
+ border: 1px solid var(--gray-300);
268
+ border-radius: 0.375rem;
269
+ font-size: 0.875rem;
270
+ }
271
+
272
+ .form-input:focus,
273
+ .form-select:focus,
274
+ .form-textarea:focus {
275
+ outline: none;
276
+ border-color: var(--primary);
277
+ box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
278
+ }
279
+
280
+ .form-textarea {
281
+ min-height: 150px;
282
+ font-family: monospace;
283
+ }
284
+
285
+ .form-content {
286
+ padding: 0.5rem 0;
287
+ }
288
+
289
+ .form-group label {
290
+ display: block;
291
+ font-weight: 500;
292
+ margin-bottom: 0.375rem;
293
+ font-size: 0.875rem;
294
+ color: var(--gray-700);
295
+ }
296
+
297
+ .form-control {
298
+ width: 100%;
299
+ padding: 0.625rem 0.875rem;
300
+ border: 1px solid var(--border-color);
301
+ border-radius: 0.375rem;
302
+ font-size: 0.875rem;
303
+ transition: border-color 0.15s, box-shadow 0.15s, background-color 0.3s;
304
+ background: var(--bg-primary);
305
+ color: var(--text-primary);
306
+ }
307
+
308
+ .form-control:focus {
309
+ outline: none;
310
+ border-color: var(--primary);
311
+ box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
312
+ }
313
+
314
+ .form-hint {
315
+ display: block;
316
+ margin-top: 0.375rem;
317
+ font-size: 0.75rem;
318
+ color: var(--gray-500);
319
+ }
320
+
321
+ .code-editor {
322
+ font-family: 'SF Mono', Consolas, monospace;
323
+ font-size: 0.8125rem;
324
+ min-height: 200px;
325
+ }
326
+
327
+ .form-actions {
328
+ display: flex;
329
+ gap: 0.75rem;
330
+ margin-top: 1.5rem;
331
+ padding-top: 1.5rem;
332
+ border-top: 1px solid var(--gray-200);
333
+ }
334
+
335
+ /* Filters */
336
+ .filters {
337
+ display: flex;
338
+ gap: 1rem;
339
+ flex-wrap: wrap;
340
+ align-items: flex-end;
341
+ margin-bottom: 1.5rem;
342
+ padding: 1rem;
343
+ background: white;
344
+ border-radius: 0.5rem;
345
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
346
+ }
347
+
348
+ .filter-group {
349
+ flex: 1;
350
+ min-width: 150px;
351
+ }
352
+
353
+ .filter-group label {
354
+ display: block;
355
+ font-size: 0.75rem;
356
+ color: var(--gray-500);
357
+ margin-bottom: 0.25rem;
358
+ }
359
+
360
+ .filters-form {
361
+ padding: 1rem;
362
+ }
363
+
364
+ .filters-grid {
365
+ display: grid;
366
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
367
+ gap: 1rem;
368
+ margin-bottom: 1rem;
369
+ }
370
+
371
+ .filter-group label {
372
+ display: block;
373
+ font-size: 0.75rem;
374
+ font-weight: 500;
375
+ color: var(--gray-500);
376
+ margin-bottom: 0.25rem;
377
+ text-transform: uppercase;
378
+ letter-spacing: 0.05em;
379
+ }
380
+
381
+ .filter-group input,
382
+ .filter-group select {
383
+ width: 100%;
384
+ padding: 0.5rem 0.75rem;
385
+ border: 1px solid var(--border-color);
386
+ border-radius: 0.375rem;
387
+ font-size: 0.875rem;
388
+ background: var(--bg-primary);
389
+ color: var(--text-primary);
390
+ transition: background-color 0.3s, border-color 0.2s;
391
+ }
392
+
393
+ .filter-group input:focus,
394
+ .filter-group select:focus {
395
+ outline: none;
396
+ border-color: var(--primary);
397
+ box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
398
+ }
399
+
400
+ .filter-actions {
401
+ display: flex;
402
+ gap: 0.5rem;
403
+ }
404
+
405
+ /* Pagination */
406
+ .pagination {
407
+ display: flex;
408
+ justify-content: center;
409
+ align-items: center;
410
+ gap: 0.5rem;
411
+ margin-top: 1.5rem;
412
+ padding: 1rem;
413
+ }
414
+
415
+ .pagination-link {
416
+ padding: 0.5rem 1rem;
417
+ border-radius: 0.375rem;
418
+ text-decoration: none;
419
+ color: var(--text-secondary);
420
+ background: var(--bg-primary);
421
+ border: 1px solid var(--border-color);
422
+ font-size: 0.875rem;
423
+ transition: background-color 0.2s;
424
+ }
425
+
426
+ .pagination-link:hover:not(.disabled) {
427
+ background: var(--gray-100);
428
+ }
429
+
430
+ .pagination-link.disabled {
431
+ color: var(--gray-300);
432
+ cursor: not-allowed;
433
+ }
434
+
435
+ .pagination-info {
436
+ font-size: 0.875rem;
437
+ color: var(--text-secondary);
438
+ }
439
+
440
+ /* Timeline */
441
+ .timeline {
442
+ position: relative;
443
+ padding: 1rem;
444
+ }
445
+
446
+ .timeline-item {
447
+ display: flex;
448
+ gap: 1rem;
449
+ padding-bottom: 1.5rem;
450
+ position: relative;
451
+ }
452
+
453
+ .timeline-item:not(:last-child)::after {
454
+ content: '';
455
+ position: absolute;
456
+ left: 6px;
457
+ top: 24px;
458
+ bottom: 0;
459
+ width: 2px;
460
+ background: var(--gray-200);
461
+ }
462
+
463
+ .timeline-marker {
464
+ width: 14px;
465
+ height: 14px;
466
+ border-radius: 50%;
467
+ background: var(--gray-300);
468
+ flex-shrink: 0;
469
+ margin-top: 4px;
470
+ }
471
+
472
+ .timeline-marker.badge-delivered {
473
+ background: var(--success);
474
+ }
475
+
476
+ .timeline-marker.badge-opened {
477
+ background: var(--warning);
478
+ }
479
+
480
+ .timeline-marker.badge-clicked {
481
+ background: var(--purple-text);
482
+ }
483
+
484
+ .timeline-marker.badge-bounced,
485
+ .timeline-marker.badge-complained {
486
+ background: var(--danger);
487
+ }
488
+
489
+ .timeline-content {
490
+ flex: 1;
491
+ }
492
+
493
+ .timeline-header {
494
+ display: flex;
495
+ align-items: center;
496
+ gap: 0.75rem;
497
+ }
498
+
499
+ .timeline-time {
500
+ font-size: 0.75rem;
501
+ color: var(--gray-500);
502
+ }
503
+
504
+ .timeline-details {
505
+ margin-top: 0.5rem;
506
+ font-size: 0.875rem;
507
+ color: var(--gray-700);
508
+ }
509
+
510
+ /* Detail */
511
+ .details-grid {
512
+ padding: 0.5rem 0;
513
+ }
514
+
515
+ .detail-row {
516
+ display: flex;
517
+ padding: 0.75rem 0;
518
+ border-bottom: 1px solid var(--gray-100);
519
+ }
520
+
521
+ .detail-row:last-child {
522
+ border-bottom: none;
523
+ }
524
+
525
+ .detail-label {
526
+ width: 140px;
527
+ flex-shrink: 0;
528
+ font-size: 0.875rem;
529
+ font-weight: 500;
530
+ color: var(--gray-500);
531
+ }
532
+
533
+ .detail-value {
534
+ flex: 1;
535
+ font-size: 0.875rem;
536
+ }
537
+
538
+ /* Code Block */
539
+ .code-block {
540
+ background: var(--gray-50);
541
+ border-radius: 0.375rem;
542
+ padding: 1rem;
543
+ overflow-x: auto;
544
+ }
545
+
546
+ .code-block pre {
547
+ margin: 0;
548
+ font-family: 'SF Mono', Consolas, monospace;
549
+ font-size: 0.8125rem;
550
+ white-space: pre-wrap;
551
+ word-break: break-all;
552
+ }
553
+
554
+ code {
555
+ background: var(--gray-100);
556
+ padding: 0.125rem 0.375rem;
557
+ border-radius: 0.25rem;
558
+ font-family: 'SF Mono', Consolas, monospace;
559
+ font-size: 0.8125rem;
560
+ }
561
+
562
+ /* Alert */
563
+ .alert {
564
+ padding: 1rem;
565
+ border-radius: 0.375rem;
566
+ margin-bottom: 1.25rem;
567
+ }
568
+
569
+ .alert-danger {
570
+ background: var(--danger-bg);
571
+ color: var(--danger-text);
572
+ border: 1px solid var(--danger-border);
573
+ }
574
+
575
+ .alert ul {
576
+ margin: 0.5rem 0 0 1.25rem;
577
+ }
578
+
579
+ /* Tips */
580
+ .tips-content {
581
+ padding: 0.5rem 0;
582
+ font-size: 0.875rem;
583
+ color: var(--gray-700);
584
+ }
585
+
586
+ .tips-content h4 {
587
+ font-size: 0.8125rem;
588
+ font-weight: 600;
589
+ color: var(--gray-900);
590
+ margin-bottom: 0.25rem;
591
+ }
592
+
593
+ .tips-content p {
594
+ margin-bottom: 0.5rem;
595
+ }
596
+
597
+ .tips-content code {
598
+ font-size: 0.75rem;
599
+ }
600
+
601
+ .tips-content pre {
602
+ background: var(--gray-50);
603
+ padding: 0.5rem;
604
+ border-radius: 0.25rem;
605
+ font-size: 0.75rem;
606
+ overflow-x: auto;
607
+ }
608
+
609
+ /* Header Actions */
610
+ .header-actions {
611
+ display: flex;
612
+ align-items: center;
613
+ gap: 0.5rem;
614
+ }
615
+
616
+ /* Event Details */
617
+ .event-details {
618
+ margin-top: 0.5rem;
619
+ background: var(--gray-50);
620
+ padding: 0.75rem;
621
+ border-radius: 0.25rem;
622
+ font-size: 0.75rem;
623
+ max-height: 200px;
624
+ overflow: auto;
625
+ }
626
+
627
+ /* Empty State */
628
+ .empty-state {
629
+ text-align: center;
630
+ padding: 2rem;
631
+ color: var(--gray-500);
632
+ }