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
@@ -1,59 +1,5 @@
1
- <style>
2
- .preview-tabs {
3
- display: flex;
4
- gap: 0;
5
- margin-bottom: -1px;
6
- position: relative;
7
- z-index: 1;
8
- }
9
- .preview-tab {
10
- padding: 0.5rem 1rem;
11
- background: var(--bg-secondary);
12
- border: 1px solid var(--border-color);
13
- border-bottom: none;
14
- border-radius: 0.375rem 0.375rem 0 0;
15
- cursor: pointer;
16
- font-size: 0.8125rem;
17
- font-weight: 500;
18
- color: var(--text-secondary);
19
- transition: all 0.15s;
20
- display: flex;
21
- align-items: center;
22
- gap: 0.375rem;
23
- }
24
- .preview-tab:hover { color: var(--text-primary); }
25
- .preview-tab.active {
26
- background: var(--bg-primary);
27
- color: var(--primary);
28
- border-bottom: 1px solid var(--bg-primary);
29
- }
30
- .preview-tab svg { width: 0.875rem; height: 0.875rem; }
31
- .preview-container { position: relative; }
32
- .preview-panel { display: none; }
33
- .preview-panel.active { display: block; }
34
- .preview-frame {
35
- width: 100%;
36
- min-height: 300px;
37
- border: 1px solid var(--border-color);
38
- border-radius: 0 0.375rem 0.375rem 0.375rem;
39
- background: #fff;
40
- }
41
- .preview-text {
42
- width: 100%;
43
- min-height: 200px;
44
- padding: 1rem;
45
- border: 1px solid var(--border-color);
46
- border-radius: 0 0.375rem 0.375rem 0.375rem;
47
- background: var(--bg-primary);
48
- font-family: 'SF Mono', Consolas, monospace;
49
- font-size: 0.8125rem;
50
- white-space: pre-wrap;
51
- word-break: break-word;
52
- color: var(--text-primary);
53
- margin: 0;
54
- overflow: auto;
55
- }
56
- </style>
1
+ <%= stylesheet_link_tag 'sincerely/template_form' %>
2
+
57
3
 
58
4
  <%= form_with model: template, url: form_url, local: true, class: 'template-form' do |f| %>
59
5
  <% if template.errors.any? %>
@@ -121,11 +67,11 @@
121
67
  <div class="preview-container" data-preview="html">
122
68
  <div class="preview-tabs">
123
69
  <button type="button" class="preview-tab active" data-target="code">
124
- <svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"></path></svg>
70
+ <%= inline_svg 'sincerely/code' %>
125
71
  Code
126
72
  </button>
127
73
  <button type="button" class="preview-tab" data-target="preview">
128
- <svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path></svg>
74
+ <%= inline_svg 'sincerely/eye' %>
129
75
  Preview
130
76
  </button>
131
77
  </div>
@@ -152,11 +98,11 @@
152
98
  <div class="preview-container" data-preview="text">
153
99
  <div class="preview-tabs">
154
100
  <button type="button" class="preview-tab active" data-target="code">
155
- <svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"></path></svg>
101
+ <%= inline_svg 'sincerely/code' %>
156
102
  Code
157
103
  </button>
158
104
  <button type="button" class="preview-tab" data-target="preview">
159
- <svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path></svg>
105
+ <%= inline_svg 'sincerely/eye' %>
160
106
  Preview
161
107
  </button>
162
108
  </div>
@@ -174,53 +120,8 @@ Welcome to our platform.' %>
174
120
 
175
121
  <div class="form-actions">
176
122
  <%= f.submit template.persisted? ? 'Update Template' : 'Create Template', class: 'btn btn-primary' %>
177
- <a href="<%= template.persisted? ? sincerely.template_path(template) : sincerely.templates_path %>" class="btn btn-secondary">Cancel</a>
123
+ <a href="<%= template.persisted? ? template_path(template) : templates_path %>" class="btn btn-secondary">Cancel</a>
178
124
  </div>
179
125
  <% end %>
180
126
 
181
- <script>
182
- (function() {
183
- function updateHtmlPreview() {
184
- const textarea = document.getElementById('html_content');
185
- const iframe = document.getElementById('html_preview');
186
- if (!textarea || !iframe) return;
187
-
188
- iframe.srcdoc = textarea.value || '';
189
- }
190
-
191
- function updateTextPreview() {
192
- const textarea = document.getElementById('text_content');
193
- const preview = document.getElementById('text_preview');
194
- if (!textarea || !preview) return;
195
-
196
- preview.textContent = textarea.value || '';
197
- }
198
-
199
- document.querySelectorAll('.preview-container').forEach(container => {
200
- const tabs = container.querySelectorAll('.preview-tab');
201
- const panels = container.querySelectorAll('.preview-panel');
202
- const previewType = container.dataset.preview;
203
-
204
- tabs.forEach(tab => {
205
- tab.addEventListener('click', () => {
206
- const target = tab.dataset.target;
207
-
208
- tabs.forEach(t => t.classList.remove('active'));
209
- tab.classList.add('active');
210
-
211
- panels.forEach(p => {
212
- p.classList.toggle('active', p.dataset.panel === target);
213
- });
214
-
215
- if (target === 'preview') {
216
- if (previewType === 'html') {
217
- updateHtmlPreview();
218
- } else if (previewType === 'text') {
219
- updateTextPreview();
220
- }
221
- }
222
- });
223
- });
224
- });
225
- })();
226
- </script>
127
+ <%= javascript_include_tag 'sincerely/template_form' %>
@@ -1,11 +1,11 @@
1
1
  <div class="page-header page-header--visible">
2
2
  <div class="page-header-top">
3
3
  <button onclick="history.back()" class="btn-back" title="Back">
4
- <svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path></svg>
4
+ <%= inline_svg 'sincerely/chevron_left' %>
5
5
  </button>
6
6
  <h1 class="page-title">Edit Template</h1>
7
7
  </div>
8
8
  <p class="page-subtitle">Update <%= @template.name %></p>
9
9
  </div>
10
10
 
11
- <%= render 'form', template: @template, form_url: sincerely.template_path(@template) %>
11
+ <%= render 'form', template: @template, form_url: template_path(@template) %>
@@ -6,10 +6,8 @@
6
6
  <div class="card">
7
7
  <div class="card-header">
8
8
  <h2 class="card-title">All Templates</h2>
9
- <a href="<%= sincerely.new_template_path %>" class="btn btn-primary">
10
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
11
- <path d="M12 5v14M5 12h14"></path>
12
- </svg>
9
+ <a href="<%= new_template_path %>" class="btn btn-primary">
10
+ <%= inline_svg 'sincerely/plus' %>
13
11
  New Template
14
12
  </a>
15
13
  </div>
@@ -32,24 +30,18 @@
32
30
  <td><%= truncate(template.sender, length: 30) %></td>
33
31
  <td><%= template.created_at.strftime('%b %d, %Y') %></td>
34
32
  <td class="actions-cell">
35
- <a href="<%= sincerely.template_path(template) %>" class="btn-icon" title="View">
36
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
37
- <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
38
- <circle cx="12" cy="12" r="3"></circle>
39
- </svg>
33
+ <a href="<%= template_path(template) %>" class="btn-icon" title="View">
34
+ <%= inline_svg 'sincerely/eye' %>
40
35
  </a>
41
- <a href="<%= sincerely.edit_template_path(template) %>" class="btn-icon" title="Edit">
42
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
43
- <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
44
- <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
45
- </svg>
36
+ <a href="<%= edit_template_path(template) %>" class="btn-icon" title="Edit">
37
+ <%= inline_svg 'sincerely/pencil' %>
46
38
  </a>
47
39
  </td>
48
40
  </tr>
49
41
  <% end %>
50
42
  <% if @templates.empty? %>
51
43
  <tr>
52
- <td colspan="5" class="empty-state">No templates found. <a href="<%= sincerely.new_template_path %>">Create one</a></td>
44
+ <td colspan="5" class="empty-state">No templates found. <a href="<%= new_template_path %>">Create one</a></td>
53
45
  </tr>
54
46
  <% end %>
55
47
  </tbody>
@@ -1,11 +1,11 @@
1
1
  <div class="page-header page-header--visible">
2
2
  <div class="page-header-top">
3
3
  <button onclick="history.back()" class="btn-back" title="Back">
4
- <svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path></svg>
4
+ <%= inline_svg 'sincerely/chevron_left' %>
5
5
  </button>
6
6
  <h1 class="page-title">New Template</h1>
7
7
  </div>
8
8
  <p class="page-subtitle">Create a new notification template</p>
9
9
  </div>
10
10
 
11
- <%= render 'form', template: @template, form_url: sincerely.templates_path %>
11
+ <%= render 'form', template: @template, form_url: templates_path %>
@@ -2,20 +2,8 @@
2
2
  <html>
3
3
  <head>
4
4
  <title>Preview: <%= @template.name %></title>
5
- <style>
6
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 0; padding: 0; background: #f5f5f5; }
7
- .preview-header { background: #1f2937; color: white; padding: 1rem 2rem; }
8
- .preview-header h1 { margin: 0; font-size: 1.25rem; }
9
- .preview-meta { margin-top: 0.5rem; font-size: 0.875rem; opacity: 0.8; }
10
- .preview-tabs { display: flex; gap: 0; background: #374151; }
11
- .preview-tab { padding: 0.75rem 1.5rem; color: #9ca3af; cursor: pointer; border: none; background: none; font-size: 0.875rem; }
12
- .preview-tab.active { color: white; background: #4f46e5; }
13
- .preview-content { padding: 2rem; }
14
- .preview-frame { background: white; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); overflow: hidden; }
15
- .preview-frame-html { padding: 0; }
16
- .preview-frame-html iframe { width: 100%; height: 600px; border: none; }
17
- .preview-frame-text { padding: 2rem; white-space: pre-wrap; font-family: monospace; background: #fafafa; }
18
- </style>
5
+ <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self'; object-src 'none'">
6
+ <%= stylesheet_link_tag 'sincerely/template_preview' %>
19
7
  </head>
20
8
  <body>
21
9
  <div class="preview-header">
@@ -25,24 +13,17 @@
25
13
  </div>
26
14
  </div>
27
15
  <div class="preview-tabs">
28
- <button class="preview-tab active" onclick="showTab('html')">HTML</button>
29
- <button class="preview-tab" onclick="showTab('text')">Text</button>
16
+ <button class="preview-tab active" data-tab="html">HTML</button>
17
+ <button class="preview-tab" data-tab="text">Text</button>
30
18
  </div>
31
19
  <div class="preview-content">
32
20
  <div id="html-preview" class="preview-frame preview-frame-html">
33
- <iframe srcdoc="<%= ERB::Util.html_escape(@rendered_html) %>"></iframe>
21
+ <iframe srcdoc="<%= ERB::Util.html_escape(@rendered_html) %>" sandbox="allow-same-origin"></iframe>
34
22
  </div>
35
- <div id="text-preview" class="preview-frame preview-frame-text" style="display: none;">
23
+ <div id="text-preview" class="preview-frame preview-frame-text preview-frame--hidden">
36
24
  <%= @rendered_text %>
37
25
  </div>
38
26
  </div>
39
- <script>
40
- function showTab(tab) {
41
- document.querySelectorAll('.preview-tab').forEach(t => t.classList.remove('active'));
42
- document.querySelector('.preview-tab[onclick="showTab(\'' + tab + '\')"]').classList.add('active');
43
- document.getElementById('html-preview').style.display = tab === 'html' ? 'block' : 'none';
44
- document.getElementById('text-preview').style.display = tab === 'text' ? 'block' : 'none';
45
- }
46
- </script>
27
+ <%= javascript_include_tag 'sincerely/template_preview' %>
47
28
  </body>
48
29
  </html>
@@ -1,7 +1,7 @@
1
1
  <div class="page-header page-header--visible">
2
2
  <div class="page-header-top">
3
3
  <button onclick="history.back()" class="btn-back" title="Back">
4
- <svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path></svg>
4
+ <%= inline_svg 'sincerely/chevron_left' %>
5
5
  </button>
6
6
  <h1 class="page-title"><%= @template.name %></h1>
7
7
  </div>
@@ -13,8 +13,8 @@
13
13
  <div class="card-header">
14
14
  <h2 class="card-title">Details</h2>
15
15
  <div class="header-actions">
16
- <a href="<%= sincerely.preview_template_path(@template) %>" class="btn btn-secondary btn-sm" target="_blank">Preview</a>
17
- <a href="<%= sincerely.edit_template_path(@template) %>" class="btn btn-primary btn-sm">Edit</a>
16
+ <a href="<%= preview_template_path(@template) %>" class="btn btn-secondary btn-sm" target="_blank">Preview</a>
17
+ <a href="<%= edit_template_path(@template) %>" class="btn btn-primary btn-sm">Edit</a>
18
18
  </div>
19
19
  </div>
20
20
  <div class="details-grid">
@@ -11,13 +11,13 @@ module Sincerely
11
11
 
12
12
  # Return navigation (optional)
13
13
  # Set return_url to show a "back to app" link in the footer
14
- attr_accessor :return_url, :return_label
14
+ attr_config :return_url, :return_label
15
15
 
16
16
  # Logout configuration (optional)
17
17
  # Set logout_url to show a logout button in the navigation
18
18
  # The button only appears when authentication is configured
19
19
  # Set logout_method to :delete for Devise (default is :get)
20
- attr_accessor :logout_url, :logout_label, :logout_method
20
+ attr_config :logout_url, :logout_label, :logout_method
21
21
 
22
22
  # Notification filtering (optional)
23
23
  # Lambda that returns a hash for where() clause to filter notifications
@@ -57,8 +57,8 @@ module Sincerely
57
57
  end
58
58
 
59
59
  def subject
60
- raw_subject = notification.delivery_options&.fetch('subject', nil) || template.subject
61
- template.renderer.render(raw_subject, notification.delivery_options&.stringify_keys)
60
+ raw_subject = notification.delivery_options_hash.fetch('subject', nil) || template.subject || ''
61
+ template.renderer.render(raw_subject, notification.delivery_options_hash)
62
62
  end
63
63
 
64
64
  def update_notification(response)
@@ -3,5 +3,9 @@
3
3
  module Sincerely
4
4
  class Engine < ::Rails::Engine
5
5
  isolate_namespace Sincerely
6
+
7
+ initializer 'sincerely.assets.precompile' do |app|
8
+ app.config.assets.precompile += %w[sincerely.js] if app.config.respond_to?(:assets)
9
+ end
6
10
  end
7
11
  end
@@ -68,7 +68,11 @@ module Sincerely
68
68
  end
69
69
 
70
70
  def render_content(content_type)
71
- template.render(content_type, delivery_options)
71
+ template.render(content_type, delivery_options_hash)
72
+ end
73
+
74
+ def delivery_options_hash
75
+ delivery_options&.stringify_keys || {}
72
76
  end
73
77
 
74
78
  def deliver
@@ -21,24 +21,26 @@ module Sincerely
21
21
 
22
22
  case event_type.to_sym
23
23
  when :bounce
24
- notification.set_bounced!
24
+ notification.set_bounced! if notification.may_set_bounced?
25
25
  create_event
26
26
  when :complaint
27
- notification.set_complained!
27
+ notification.set_complained! if notification.may_set_complained?
28
28
  create_event
29
29
  when :delivery
30
- notification.set_delivered!
30
+ notification.set_delivered! if notification.may_set_delivered?
31
31
  create_event
32
32
  when :send
33
33
  notification.set_accepted! if notification.may_set_accepted?
34
34
  when :reject
35
- notification.set_rejected!
36
- notification.update(error_message: event.rejection_reason)
35
+ if notification.may_set_rejected?
36
+ notification.set_rejected!
37
+ notification.update(error_message: event.rejection_reason)
38
+ end
37
39
  when :open
38
- notification.set_opened!
40
+ notification.set_opened! if notification.may_set_opened?
39
41
  create_event
40
42
  when :click
41
- notification.set_clicked!
43
+ notification.set_clicked! if notification.may_set_clicked?
42
44
  create_event
43
45
  end
44
46
  end
@@ -10,7 +10,7 @@ module Sincerely
10
10
  validates :name, presence: true
11
11
 
12
12
  def render(content_type, options = {})
13
- content = public_send("#{content_type}_content")
13
+ content = public_send("#{content_type}_content") || ''
14
14
  renderer.render(content, options&.stringify_keys)
15
15
  end
16
16
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sincerely
4
- VERSION = '1.0.0'
4
+ VERSION = '1.1.0'
5
5
  end
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sincerely
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - 100 Starlings
8
+ autorequire:
8
9
  bindir: exe
9
10
  cert_chain: []
10
- date: 2026-03-31 00:00:00.000000000 Z
11
+ date: 2026-08-28 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: aasm
@@ -93,6 +94,7 @@ dependencies:
93
94
  - - ">="
94
95
  - !ruby/object:Gem::Version
95
96
  version: '6'
97
+ description:
96
98
  email:
97
99
  - rubyblok@100starlings.com
98
100
  executables: []
@@ -104,7 +106,46 @@ files:
104
106
  - CHANGELOG.md
105
107
  - README.md
106
108
  - Rakefile
109
+ - app/assets/config/sincerely.js
110
+ - app/assets/images/sincerely/arrow_left.svg
111
+ - app/assets/images/sincerely/check.svg
112
+ - app/assets/images/sincerely/chevron_left.svg
113
+ - app/assets/images/sincerely/clock.svg
114
+ - app/assets/images/sincerely/code.svg
115
+ - app/assets/images/sincerely/eye.svg
116
+ - app/assets/images/sincerely/logout.svg
117
+ - app/assets/images/sincerely/menu.svg
118
+ - app/assets/images/sincerely/moon.svg
119
+ - app/assets/images/sincerely/pencil.svg
120
+ - app/assets/images/sincerely/plus.svg
121
+ - app/assets/images/sincerely/progress_spinner.svg
122
+ - app/assets/images/sincerely/spinner.svg
123
+ - app/assets/images/sincerely/sun.svg
124
+ - app/assets/images/sincerely/warning_circle.svg
125
+ - app/assets/javascripts/sincerely/charts.js
126
+ - app/assets/javascripts/sincerely/clickable_rows.js
127
+ - app/assets/javascripts/sincerely/layout.js
128
+ - app/assets/javascripts/sincerely/send_form.js
129
+ - app/assets/javascripts/sincerely/template_form.js
130
+ - app/assets/javascripts/sincerely/template_preview.js
131
+ - app/assets/javascripts/sincerely/theme_init.js
132
+ - app/assets/stylesheets/sincerely/charts.css
133
+ - app/assets/stylesheets/sincerely/components.css
134
+ - app/assets/stylesheets/sincerely/layout.css
135
+ - app/assets/stylesheets/sincerely/navigation.css
136
+ - app/assets/stylesheets/sincerely/send_form.css
137
+ - app/assets/stylesheets/sincerely/template_form.css
138
+ - app/assets/stylesheets/sincerely/template_preview.css
139
+ - app/assets/stylesheets/sincerely/variables.css
107
140
  - app/controllers/sincerely/application_controller.rb
141
+ - app/controllers/sincerely/concerns/authorization.rb
142
+ - app/controllers/sincerely/concerns/delivery_metrics.rb
143
+ - app/controllers/sincerely/concerns/event_filtering.rb
144
+ - app/controllers/sincerely/concerns/event_timeline.rb
145
+ - app/controllers/sincerely/concerns/notification_timeline.rb
146
+ - app/controllers/sincerely/concerns/notifications.rb
147
+ - app/controllers/sincerely/concerns/pagination.rb
148
+ - app/controllers/sincerely/concerns/periods.rb
108
149
  - app/controllers/sincerely/dashboard_controller.rb
109
150
  - app/controllers/sincerely/delivery_events_controller.rb
110
151
  - app/controllers/sincerely/engagement_events_controller.rb
@@ -112,6 +153,7 @@ files:
112
153
  - app/controllers/sincerely/send_controller.rb
113
154
  - app/controllers/sincerely/templates_controller.rb
114
155
  - app/helpers/sincerely/application_helper.rb
156
+ - app/helpers/sincerely/chart_helper.rb
115
157
  - app/views/layouts/sincerely/application.html.erb
116
158
  - app/views/sincerely/dashboard/index.html.erb
117
159
  - app/views/sincerely/delivery_events/index.html.erb
@@ -119,7 +161,12 @@ files:
119
161
  - app/views/sincerely/notifications/index.html.erb
120
162
  - app/views/sincerely/notifications/show.html.erb
121
163
  - app/views/sincerely/send/new.html.erb
164
+ - app/views/sincerely/shared/_event.html.erb
165
+ - app/views/sincerely/shared/_line_chart.html.erb
166
+ - app/views/sincerely/shared/_notification_type_filter.html.erb
122
167
  - app/views/sincerely/shared/_pagination.html.erb
168
+ - app/views/sincerely/shared/_status_option.html.erb
169
+ - app/views/sincerely/shared/_template_option.html.erb
123
170
  - app/views/sincerely/templates/_form.html.erb
124
171
  - app/views/sincerely/templates/edit.html.erb
125
172
  - app/views/sincerely/templates/index.html.erb
@@ -159,7 +206,6 @@ files:
159
206
  - lib/sincerely/templates/email_liquid_template.rb
160
207
  - lib/sincerely/templates/notification_template.rb
161
208
  - lib/sincerely/version.rb
162
- - sincerely.gemspec
163
209
  homepage: http://www.rubyblok.com
164
210
  licenses:
165
211
  - MIT
@@ -167,6 +213,7 @@ metadata:
167
213
  homepage_uri: http://www.rubyblok.com
168
214
  source_code_uri: https://github.com/100Starlings/sincerely
169
215
  rubygems_mfa_required: 'true'
216
+ post_install_message:
170
217
  rdoc_options: []
171
218
  require_paths:
172
219
  - lib
@@ -181,7 +228,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
181
228
  - !ruby/object:Gem::Version
182
229
  version: '0'
183
230
  requirements: []
184
- rubygems_version: 3.6.2
231
+ rubygems_version: 3.5.22
232
+ signing_key:
185
233
  specification_version: 4
186
234
  summary: Notifications
187
235
  test_files: []
data/sincerely.gemspec DELETED
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'lib/sincerely/version'
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = 'sincerely'
7
- spec.version = Sincerely::VERSION
8
- spec.authors = ['100 Starlings']
9
- spec.email = ['rubyblok@100starlings.com']
10
-
11
- spec.summary = 'Notifications'
12
- spec.homepage = 'http://www.rubyblok.com'
13
- spec.license = 'MIT'
14
- spec.required_ruby_version = '>= 3.1.0'
15
-
16
- spec.metadata['homepage_uri'] = spec.homepage
17
- spec.metadata['source_code_uri'] = 'https://github.com/100Starlings/sincerely'
18
-
19
- spec.files = Dir.chdir(__dir__) do
20
- `git ls-files -z`.split("\x0").reject do |f|
21
- (File.expand_path(f) == __FILE__) ||
22
- f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor Gemfile])
23
- end
24
- end
25
- spec.bindir = 'exe'
26
- spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
27
- spec.require_paths = ['lib']
28
-
29
- spec.add_dependency 'aasm', '~> 5.3'
30
- spec.add_dependency 'anyway_config', '~> 2.6'
31
- spec.add_dependency 'aws-sdk-sesv2', '~> 1.54'
32
- spec.add_dependency 'aws-sdk-sns', '~> 1.82'
33
- spec.add_dependency 'liquid', '~> 5.5'
34
- spec.add_dependency 'railties', '>= 6'
35
-
36
- spec.metadata['rubygems_mfa_required'] = 'true'
37
- end