profitable 0.3.0 → 0.5.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.
@@ -1,4 +1,9 @@
1
1
  <style>
2
+ main h2.title {
3
+ font-size: 1.25rem;
4
+ margin: 64px 0 12px 0;
5
+ }
6
+
2
7
  .card-grid {
3
8
  display: flex;
4
9
  flex-wrap: wrap;
@@ -32,8 +37,8 @@
32
37
 
33
38
  <header>
34
39
  <h1>💸 <%= Rails.application.class.module_parent_name %></h1>
35
- <% if Profitable.mrr_growth_rate > 0 %>
36
- <p><%= Profitable.time_to_next_mrr_milestone %></p>
40
+ <% if @show_milestone %>
41
+ <p><%= @milestone_message %></p>
37
42
  <% end %>
38
43
  </header>
39
44
 
@@ -41,69 +46,201 @@
41
46
 
42
47
  <div class="card-grid">
43
48
  <div class="card">
44
- <h2><%= Profitable.total_customers.to_readable %></h2>
49
+ <h2><%= @total_customers.to_readable %></h2>
45
50
  <p>total customers</p>
46
51
  </div>
47
52
  <div class="card">
48
- <h2><%= Profitable.mrr.to_readable %></h2>
53
+ <h2><%= @mrr.to_readable %></h2>
49
54
  <p>MRR</p>
50
55
  </div>
51
56
  <div class="card">
52
- <h2><%= Profitable.estimated_valuation.to_readable %></h2>
57
+ <h2><%= @ttm_revenue.to_readable %></h2>
58
+ <p>TTM revenue</p>
59
+ </div>
60
+ <div class="card">
61
+ <h2><%= @estimated_valuation.to_readable %></h2>
53
62
  <p>Valuation at 3x ARR</p>
54
63
  </div>
55
64
  <div class="card">
56
- <h2><%= Profitable.mrr_growth_rate.to_readable %></h2>
65
+ <h2><%= @mrr_growth_rate.to_readable %></h2>
57
66
  <p>MRR growth rate</p>
58
67
  </div>
59
68
  <div class="card">
60
- <h2><%= Profitable.average_revenue_per_customer.to_readable %></h2>
69
+ <h2><%= @average_revenue_per_customer.to_readable %></h2>
61
70
  <p>ARPC</p>
62
71
  </div>
63
72
  <div class="card">
64
- <h2><%= Profitable.lifetime_value.to_readable %></h2>
73
+ <h2><%= @lifetime_value.to_readable %></h2>
65
74
  <p>LTV</p>
66
75
  </div>
67
76
  <div class="card">
68
- <h2><%= Profitable.all_time_revenue.to_readable %></h2>
77
+ <h2><%= @all_time_revenue.to_readable %></h2>
69
78
  <p>All-time revenue</p>
70
79
  </div>
71
80
  </div>
72
81
 
73
- <% [24.hours, 7.days, 30.days].each do |period| %>
82
+ <h2 class="title">Monthly summary</h2>
83
+ <small>(last 12 months)</small>
84
+
85
+ <style>
86
+ .summary-table {
87
+ width: 100%;
88
+ border-collapse: collapse;
89
+ margin-top: 16px;
90
+ background-color: var(--bg);
91
+ border: 1px solid var(--border);
92
+ border-radius: var(--standard-border-radius);
93
+ overflow: hidden;
94
+ }
95
+
96
+ .summary-table th,
97
+ .summary-table td {
98
+ padding: 12px 16px;
99
+ text-align: left;
100
+ border-bottom: 1px solid var(--border);
101
+ }
102
+
103
+ .summary-table th {
104
+ font-weight: 600;
105
+ background-color: var(--bg);
106
+ position: sticky;
107
+ top: 0;
108
+ }
109
+
110
+ .summary-table tr:last-child td {
111
+ border-bottom: none;
112
+ }
113
+
114
+ .summary-table .positive {
115
+ color: #10b981;
116
+ }
117
+
118
+ .summary-table .negative {
119
+ color: #ef4444;
120
+ }
121
+
122
+ .summary-table .muted {
123
+ color: #6b7280;
124
+ font-size: 0.875em;
125
+ }
126
+
127
+ .summary-table tbody tr:hover {
128
+ background-color: rgba(0, 0, 0, 0.02);
129
+ }
130
+ </style>
131
+
132
+ <table class="summary-table">
133
+ <thead>
134
+ <tr>
135
+ <th>Month</th>
136
+ <th>New</th>
137
+ <th>Churned</th>
138
+ <th>Net</th>
139
+ <th>Churn %</th>
140
+ </tr>
141
+ </thead>
142
+ <tbody>
143
+ <% @monthly_summary.reverse.each do |month_data| %>
144
+ <tr>
145
+ <td><strong><%= month_data[:month_date].strftime('%b %Y') %></strong></td>
146
+ <td>
147
+ <span class="positive">+<%= month_data[:new_subscribers] %></span>
148
+ <span class="muted">(~<%= number_to_currency(month_data[:new_mrr] / 100.0, precision: 0) %>)</span>
149
+ </td>
150
+ <td>
151
+ <% if month_data[:churned_subscribers] > 0 %>
152
+ <span class="negative">-<%= month_data[:churned_subscribers] %></span>
153
+ <span class="muted">(~<%= number_to_currency(month_data[:churned_mrr] / 100.0, precision: 0) %>)</span>
154
+ <% else %>
155
+ <span>0</span>
156
+ <% end %>
157
+ </td>
158
+ <td>
159
+ <% if month_data[:net_subscribers] > 0 %>
160
+ <span class="positive">+<%= month_data[:net_subscribers] %></span>
161
+ <span class="muted">(~+<%= number_to_currency(month_data[:net_mrr] / 100.0, precision: 0) %>)</span>
162
+ <% elsif month_data[:net_subscribers] < 0 %>
163
+ <span class="negative"><%= month_data[:net_subscribers] %></span>
164
+ <span class="muted">(~<%= number_to_currency(month_data[:net_mrr] / 100.0, precision: 0) %>)</span>
165
+ <% else %>
166
+ <span>0</span>
167
+ <% end %>
168
+ </td>
169
+ <td><%= month_data[:churn_rate] %>%</td>
170
+ </tr>
171
+ <% end %>
172
+ </tbody>
173
+ </table>
174
+
175
+ <h2 class="title">Daily summary</h2>
176
+ <small>(last 30 days)</small>
177
+
178
+ <table class="summary-table">
179
+ <thead>
180
+ <tr>
181
+ <th>Date</th>
182
+ <th>New Subscribers</th>
183
+ <th>Churned</th>
184
+ </tr>
185
+ </thead>
186
+ <tbody>
187
+ <% @daily_summary.reverse.each do |day_data| %>
188
+ <tr>
189
+ <td><strong><%= day_data[:date].strftime('%b %-d, %Y') %></strong></td>
190
+ <td>
191
+ <% if day_data[:new_subscribers] > 0 %>
192
+ <span class="positive">+<%= day_data[:new_subscribers] %></span>
193
+ <% else %>
194
+ <span>0</span>
195
+ <% end %>
196
+ </td>
197
+ <td>
198
+ <% if day_data[:churned_subscribers] > 0 %>
199
+ <span class="negative">-<%= day_data[:churned_subscribers] %></span>
200
+ <% else %>
201
+ <span>0</span>
202
+ <% end %>
203
+ </td>
204
+ </tr>
205
+ <% end %>
206
+ </tbody>
207
+ </table>
208
+
209
+ <% @periods.each do |period| %>
74
210
  <% period_short = period.inspect.gsub("days", "d").gsub("hours", "h").gsub(" ", "") %>
211
+ <% data = @period_data[period] %>
75
212
 
76
- <h2>Last <%= period.inspect %></h2>
213
+ <h2 class="title">Last <%= period.inspect %></h2>
77
214
 
78
215
  <div class="card-grid">
79
216
  <div class="card">
80
- <h2><%= Profitable.new_customers(in_the_last: period).to_readable %></h2>
217
+ <h2><%= data[:new_customers].to_readable %></h2>
81
218
  <p>new customers (<%= period_short %>)</p>
82
219
  </div>
83
220
  <div class="card">
84
- <h2><%= Profitable.churned_customers(in_the_last: period).to_readable %></h2>
221
+ <h2><%= data[:churned_customers].to_readable %></h2>
85
222
  <p>churned customers (<%= period_short %>)</p>
86
223
  </div>
87
224
  <div class="card">
88
- <h2><%= Profitable.churn(in_the_last: period).to_readable %></h2>
225
+ <h2><%= data[:churn].to_readable %></h2>
89
226
  <p>churn (<%= period_short %>)</p>
90
227
  </div>
91
228
 
92
229
  <div class="card">
93
- <h2><%= Profitable.new_mrr(in_the_last: period).to_readable %></h2>
230
+ <h2><%= data[:new_mrr].to_readable %></h2>
94
231
  <p>new MRR (<%= period_short %>)</p>
95
232
  </div>
96
233
  <div class="card">
97
- <h2><%= Profitable.churned_mrr(in_the_last: period).to_readable %></h2>
234
+ <h2><%= data[:churned_mrr].to_readable %></h2>
98
235
  <p>churned MRR (<%= period_short %>)</p>
99
236
  </div>
100
237
  <div class="card">
101
- <h2><%= Profitable.mrr_growth(in_the_last: period).to_readable %></h2>
238
+ <h2><%= data[:mrr_growth].to_readable %></h2>
102
239
  <p>MRR growth (<%= period_short %>)</p>
103
240
  </div>
104
241
 
105
242
  <div class="card">
106
- <h2><%= Profitable.revenue_in_period(in_the_last: period).to_readable %></h2>
243
+ <h2><%= data[:revenue].to_readable %></h2>
107
244
  <p>total revenue (<%= period_short %>)</p>
108
245
  </div>
109
246
 
data/context7.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "url": "https://context7.com/rameerez/profitable",
3
+ "public_key": "pk_HibNJE5rTFvy1txHHXUot"
4
+ }
@@ -5,18 +5,19 @@ source "https://rubygems.org"
5
5
  gem "rake", "~> 13.0"
6
6
  gem "pay", "~> 10.0.0"
7
7
  gem "stripe", "~> 15.0"
8
+ gem "rails", "~> 8.1.0"
8
9
 
9
- group :development do
10
- gem "appraisal", "~> 2.5"
11
- end
12
-
13
- group :test do
14
- gem "minitest", "~> 5.0"
15
- gem "minitest-reporters", "~> 1.6"
10
+ group :development, :test do
11
+ gem "appraisal"
12
+ gem "minitest", "~> 6.0"
13
+ gem "minitest-mock"
14
+ gem "minitest-reporters"
15
+ gem "rack-test"
16
+ gem "simplecov", require: false
17
+ gem "sqlite3", ">= 2.1"
16
18
  gem "mocha", "~> 2.1"
17
19
  gem "activerecord", ">= 7.0"
18
20
  gem "actionview", ">= 7.0"
19
- gem "sqlite3"
20
21
  end
21
22
 
22
23
  gemspec path: "../"
@@ -5,18 +5,19 @@ source "https://rubygems.org"
5
5
  gem "rake", "~> 13.0"
6
6
  gem "pay", "~> 11.0"
7
7
  gem "stripe", "~> 18.0"
8
+ gem "rails", "~> 8.1.0"
8
9
 
9
- group :development do
10
- gem "appraisal", "~> 2.5"
11
- end
12
-
13
- group :test do
14
- gem "minitest", "~> 5.0"
15
- gem "minitest-reporters", "~> 1.6"
10
+ group :development, :test do
11
+ gem "appraisal"
12
+ gem "minitest", "~> 6.0"
13
+ gem "minitest-mock"
14
+ gem "minitest-reporters"
15
+ gem "rack-test"
16
+ gem "simplecov", require: false
17
+ gem "sqlite3", ">= 2.1"
16
18
  gem "mocha", "~> 2.1"
17
19
  gem "activerecord", ">= 7.0"
18
20
  gem "actionview", ">= 7.0"
19
- gem "sqlite3"
20
21
  end
21
22
 
22
23
  gemspec path: "../"
@@ -5,18 +5,19 @@ source "https://rubygems.org"
5
5
  gem "rake", "~> 13.0"
6
6
  gem "pay", "~> 7.3.0"
7
7
  gem "stripe", "~> 12.0"
8
+ gem "rails", "~> 8.1.0"
8
9
 
9
- group :development do
10
- gem "appraisal", "~> 2.5"
11
- end
12
-
13
- group :test do
14
- gem "minitest", "~> 5.0"
15
- gem "minitest-reporters", "~> 1.6"
10
+ group :development, :test do
11
+ gem "appraisal"
12
+ gem "minitest", "~> 6.0"
13
+ gem "minitest-mock"
14
+ gem "minitest-reporters"
15
+ gem "rack-test"
16
+ gem "simplecov", require: false
17
+ gem "sqlite3", ">= 2.1"
16
18
  gem "mocha", "~> 2.1"
17
19
  gem "activerecord", ">= 7.0"
18
20
  gem "actionview", ">= 7.0"
19
- gem "sqlite3"
20
21
  end
21
22
 
22
23
  gemspec path: "../"
@@ -5,18 +5,19 @@ source "https://rubygems.org"
5
5
  gem "rake", "~> 13.0"
6
6
  gem "pay", "~> 8.3.0"
7
7
  gem "stripe", "~> 13.0"
8
+ gem "rails", "~> 8.1.0"
8
9
 
9
- group :development do
10
- gem "appraisal", "~> 2.5"
11
- end
12
-
13
- group :test do
14
- gem "minitest", "~> 5.0"
15
- gem "minitest-reporters", "~> 1.6"
10
+ group :development, :test do
11
+ gem "appraisal"
12
+ gem "minitest", "~> 6.0"
13
+ gem "minitest-mock"
14
+ gem "minitest-reporters"
15
+ gem "rack-test"
16
+ gem "simplecov", require: false
17
+ gem "sqlite3", ">= 2.1"
16
18
  gem "mocha", "~> 2.1"
17
19
  gem "activerecord", ">= 7.0"
18
20
  gem "actionview", ">= 7.0"
19
- gem "sqlite3"
20
21
  end
21
22
 
22
23
  gemspec path: "../"
@@ -5,18 +5,19 @@ source "https://rubygems.org"
5
5
  gem "rake", "~> 13.0"
6
6
  gem "pay", "~> 9.0.0"
7
7
  gem "stripe", "~> 13.0"
8
+ gem "rails", "~> 8.1.0"
8
9
 
9
- group :development do
10
- gem "appraisal", "~> 2.5"
11
- end
12
-
13
- group :test do
14
- gem "minitest", "~> 5.0"
15
- gem "minitest-reporters", "~> 1.6"
10
+ group :development, :test do
11
+ gem "appraisal"
12
+ gem "minitest", "~> 6.0"
13
+ gem "minitest-mock"
14
+ gem "minitest-reporters"
15
+ gem "rack-test"
16
+ gem "simplecov", require: false
17
+ gem "sqlite3", ">= 2.1"
16
18
  gem "mocha", "~> 2.1"
17
19
  gem "activerecord", ">= 7.0"
18
20
  gem "actionview", ">= 7.0"
19
- gem "sqlite3"
20
21
  end
21
22
 
22
23
  gemspec path: "../"
@@ -0,0 +1,23 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rake", "~> 13.0"
6
+ gem "rails", "~> 7.2.0"
7
+ gem "pay", "~> 11.0"
8
+ gem "stripe", "~> 18.0"
9
+
10
+ group :development, :test do
11
+ gem "appraisal"
12
+ gem "minitest", "~> 6.0"
13
+ gem "minitest-mock"
14
+ gem "minitest-reporters"
15
+ gem "rack-test"
16
+ gem "simplecov", require: false
17
+ gem "sqlite3", ">= 2.1"
18
+ gem "mocha", "~> 2.1"
19
+ gem "activerecord", ">= 7.0"
20
+ gem "actionview", ">= 7.0"
21
+ end
22
+
23
+ gemspec path: "../"
@@ -0,0 +1,23 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rake", "~> 13.0"
6
+ gem "rails", "~> 8.1.0"
7
+ gem "pay", "~> 11.0"
8
+ gem "stripe", "~> 18.0"
9
+
10
+ group :development, :test do
11
+ gem "appraisal"
12
+ gem "minitest", "~> 6.0"
13
+ gem "minitest-mock"
14
+ gem "minitest-reporters"
15
+ gem "rack-test"
16
+ gem "simplecov", require: false
17
+ gem "sqlite3", ">= 2.1"
18
+ gem "mocha", "~> 2.1"
19
+ gem "activerecord", ">= 7.0"
20
+ gem "actionview", ">= 7.0"
21
+ end
22
+
23
+ gemspec path: "../"