loyalty_ref 0.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.
@@ -0,0 +1,233 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>LoyaltyRef Dashboard</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
16
+ background: #f8fafc;
17
+ color: #1e293b;
18
+ line-height: 1.6;
19
+ }
20
+
21
+ .navbar {
22
+ background: #1e293b;
23
+ color: white;
24
+ padding: 1rem 2rem;
25
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
26
+ }
27
+
28
+ .navbar h1 {
29
+ font-size: 1.5rem;
30
+ font-weight: 600;
31
+ }
32
+
33
+ .container {
34
+ max-width: 1200px;
35
+ margin: 0 auto;
36
+ padding: 2rem;
37
+ }
38
+
39
+ .sidebar {
40
+ background: white;
41
+ border-radius: 8px;
42
+ padding: 1.5rem;
43
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
44
+ margin-bottom: 2rem;
45
+ }
46
+
47
+ .nav-links {
48
+ display: flex;
49
+ gap: 1rem;
50
+ flex-wrap: wrap;
51
+ }
52
+
53
+ .nav-links a {
54
+ color: #64748b;
55
+ text-decoration: none;
56
+ padding: 0.5rem 1rem;
57
+ border-radius: 6px;
58
+ transition: all 0.2s;
59
+ }
60
+
61
+ .nav-links a:hover,
62
+ .nav-links a.active {
63
+ background: #f1f5f9;
64
+ color: #1e293b;
65
+ }
66
+
67
+ .card {
68
+ background: white;
69
+ border-radius: 8px;
70
+ padding: 1.5rem;
71
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
72
+ margin-bottom: 1.5rem;
73
+ }
74
+
75
+ .card h2 {
76
+ font-size: 1.25rem;
77
+ font-weight: 600;
78
+ margin-bottom: 1rem;
79
+ color: #1e293b;
80
+ }
81
+
82
+ .stats-grid {
83
+ display: grid;
84
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
85
+ gap: 1rem;
86
+ margin-bottom: 2rem;
87
+ }
88
+
89
+ .stat-card {
90
+ background: white;
91
+ padding: 1.5rem;
92
+ border-radius: 8px;
93
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
94
+ text-align: center;
95
+ }
96
+
97
+ .stat-number {
98
+ font-size: 2rem;
99
+ font-weight: 700;
100
+ color: #3b82f6;
101
+ margin-bottom: 0.5rem;
102
+ }
103
+
104
+ .stat-label {
105
+ color: #64748b;
106
+ font-size: 0.875rem;
107
+ text-transform: uppercase;
108
+ letter-spacing: 0.05em;
109
+ }
110
+
111
+ .table {
112
+ width: 100%;
113
+ border-collapse: collapse;
114
+ margin-top: 1rem;
115
+ }
116
+
117
+ .table th,
118
+ .table td {
119
+ padding: 0.75rem;
120
+ text-align: left;
121
+ border-bottom: 1px solid #e2e8f0;
122
+ }
123
+
124
+ .table th {
125
+ background: #f8fafc;
126
+ font-weight: 600;
127
+ color: #475569;
128
+ }
129
+
130
+ .table tr:hover {
131
+ background: #f8fafc;
132
+ }
133
+
134
+ .badge {
135
+ display: inline-block;
136
+ padding: 0.25rem 0.5rem;
137
+ border-radius: 4px;
138
+ font-size: 0.75rem;
139
+ font-weight: 500;
140
+ text-transform: uppercase;
141
+ }
142
+
143
+ .badge.earn {
144
+ background: #dcfce7;
145
+ color: #166534;
146
+ }
147
+
148
+ .badge.redeem {
149
+ background: #fef2f2;
150
+ color: #dc2626;
151
+ }
152
+
153
+ .badge.silver {
154
+ background: #f1f5f9;
155
+ color: #475569;
156
+ }
157
+
158
+ .badge.gold {
159
+ background: #fef3c7;
160
+ color: #92400e;
161
+ }
162
+
163
+ .badge.platinum {
164
+ background: #e0e7ff;
165
+ color: #3730a3;
166
+ }
167
+
168
+ .btn {
169
+ display: inline-block;
170
+ padding: 0.5rem 1rem;
171
+ border-radius: 6px;
172
+ text-decoration: none;
173
+ font-weight: 500;
174
+ transition: all 0.2s;
175
+ border: none;
176
+ cursor: pointer;
177
+ }
178
+
179
+ .btn-primary {
180
+ background: #3b82f6;
181
+ color: white;
182
+ }
183
+
184
+ .btn-primary:hover {
185
+ background: #2563eb;
186
+ }
187
+
188
+ .pagination {
189
+ display: flex;
190
+ justify-content: center;
191
+ gap: 0.5rem;
192
+ margin-top: 2rem;
193
+ }
194
+
195
+ .pagination a {
196
+ padding: 0.5rem 0.75rem;
197
+ border: 1px solid #e2e8f0;
198
+ border-radius: 4px;
199
+ text-decoration: none;
200
+ color: #64748b;
201
+ }
202
+
203
+ .pagination a:hover {
204
+ background: #f1f5f9;
205
+ }
206
+
207
+ .pagination .current {
208
+ background: #3b82f6;
209
+ color: white;
210
+ border-color: #3b82f6;
211
+ }
212
+ </style>
213
+ </head>
214
+ <body>
215
+ <nav class="navbar">
216
+ <h1>💎 LoyaltyRef Dashboard</h1>
217
+ </nav>
218
+
219
+ <div class="container">
220
+ <div class="sidebar">
221
+ <nav class="nav-links">
222
+ <%= link_to "Dashboard", loyalty_ref.root_path, class: current_page?(loyalty_ref.root_path) ? "active" : "" %>
223
+ <%= link_to "Users", loyalty_ref.users_path, class: current_page?(loyalty_ref.users_path) ? "active" : "" %>
224
+ <%= link_to "Transactions", loyalty_ref.transactions_path, class: current_page?(loyalty_ref.transactions_path) ? "active" : "" %>
225
+ <%= link_to "Referrals", loyalty_ref.referrals_path, class: current_page?(loyalty_ref.referrals_path) ? "active" : "" %>
226
+ <%= link_to "Analytics", loyalty_ref.analytics_path, class: current_page?(loyalty_ref.analytics_path) ? "active" : "" %>
227
+ </nav>
228
+ </div>
229
+
230
+ <%= yield %>
231
+ </div>
232
+ </body>
233
+ </html>
@@ -0,0 +1,80 @@
1
+ <div class="stats-grid">
2
+ <div class="stat-card">
3
+ <div class="stat-number"><%= @total_clicks %></div>
4
+ <div class="stat-label">Total Clicks</div>
5
+ </div>
6
+
7
+ <div class="stat-card">
8
+ <div class="stat-number"><%= @total_conversions %></div>
9
+ <div class="stat-label">Total Conversions</div>
10
+ </div>
11
+
12
+ <div class="stat-card">
13
+ <div class="stat-number"><%= number_to_percentage(@conversion_rate, precision: 1) %></div>
14
+ <div class="stat-label">Conversion Rate</div>
15
+ </div>
16
+ </div>
17
+
18
+ <div class="card">
19
+ <h2>Referrals by Device Type</h2>
20
+ <table class="table">
21
+ <thead>
22
+ <tr>
23
+ <th>Device Type</th>
24
+ <th>Count</th>
25
+ <th>Percentage</th>
26
+ </tr>
27
+ </thead>
28
+ <tbody>
29
+ <% @referrals_by_device.each do |device, count| %>
30
+ <tr>
31
+ <td><%= device.titleize %></td>
32
+ <td><%= count %></td>
33
+ <td><%= number_to_percentage((count.to_f / @total_clicks * 100), precision: 1) %></td>
34
+ </tr>
35
+ <% end %>
36
+ </tbody>
37
+ </table>
38
+ </div>
39
+
40
+ <div class="card">
41
+ <h2>Referrals by Browser</h2>
42
+ <table class="table">
43
+ <thead>
44
+ <tr>
45
+ <th>Browser</th>
46
+ <th>Count</th>
47
+ <th>Percentage</th>
48
+ </tr>
49
+ </thead>
50
+ <tbody>
51
+ <% @referrals_by_browser.each do |browser, count| %>
52
+ <tr>
53
+ <td><%= browser %></td>
54
+ <td><%= count %></td>
55
+ <td><%= number_to_percentage((count.to_f / @total_clicks * 100), precision: 1) %></td>
56
+ </tr>
57
+ <% end %>
58
+ </tbody>
59
+ </table>
60
+ </div>
61
+
62
+ <div class="card">
63
+ <h2>Daily Referral Clicks (Last 30 Days)</h2>
64
+ <table class="table">
65
+ <thead>
66
+ <tr>
67
+ <th>Date</th>
68
+ <th>Clicks</th>
69
+ </tr>
70
+ </thead>
71
+ <tbody>
72
+ <% @daily_clicks.each do |date, count| %>
73
+ <tr>
74
+ <td><%= Date.parse(date).strftime("%b %d, %Y") %></td>
75
+ <td><%= count %></td>
76
+ </tr>
77
+ <% end %>
78
+ </tbody>
79
+ </table>
80
+ </div>
@@ -0,0 +1,78 @@
1
+ <div class="stats-grid">
2
+ <div class="stat-card">
3
+ <div class="stat-number"><%= @total_users %></div>
4
+ <div class="stat-label">Total Users</div>
5
+ </div>
6
+
7
+ <div class="stat-card">
8
+ <div class="stat-number"><%= @total_transactions %></div>
9
+ <div class="stat-label">Total Transactions</div>
10
+ </div>
11
+
12
+ <div class="stat-card">
13
+ <div class="stat-number"><%= @total_referrals %></div>
14
+ <div class="stat-label">Total Referrals</div>
15
+ </div>
16
+
17
+ <div class="stat-card">
18
+ <div class="stat-number"><%= number_to_percentage(@conversion_rate, precision: 1) %></div>
19
+ <div class="stat-label">Conversion Rate</div>
20
+ </div>
21
+ </div>
22
+
23
+ <div class="card">
24
+ <h2>Recent Transactions</h2>
25
+ <table class="table">
26
+ <thead>
27
+ <tr>
28
+ <th>User</th>
29
+ <th>Type</th>
30
+ <th>Points</th>
31
+ <th>Date</th>
32
+ </tr>
33
+ </thead>
34
+ <tbody>
35
+ <% @recent_transactions.each do |transaction| %>
36
+ <tr>
37
+ <td><%= transaction.user.email rescue transaction.user.id %></td>
38
+ <td>
39
+ <span class="badge <%= transaction.transaction_type %>">
40
+ <%= transaction.transaction_type.titleize %>
41
+ </span>
42
+ </td>
43
+ <td><%= transaction.points %></td>
44
+ <td><%= transaction.created_at.strftime("%b %d, %Y") %></td>
45
+ </tr>
46
+ <% end %>
47
+ </tbody>
48
+ </table>
49
+ </div>
50
+
51
+ <div class="card">
52
+ <h2>Top Users by Points</h2>
53
+ <table class="table">
54
+ <thead>
55
+ <tr>
56
+ <th>User</th>
57
+ <th>Total Points</th>
58
+ <th>Tier</th>
59
+ </tr>
60
+ </thead>
61
+ <tbody>
62
+ <% @top_users.each do |user| %>
63
+ <tr>
64
+ <td><%= user.email rescue user.id %></td>
65
+ <td><%= LoyaltyRef.balance(user) %></td>
66
+ <td>
67
+ <% tier = LoyaltyRef.tier(user) %>
68
+ <% if tier %>
69
+ <span class="badge <%= tier.downcase %>"><%= tier %></span>
70
+ <% else %>
71
+ <span class="badge">None</span>
72
+ <% end %>
73
+ </td>
74
+ </tr>
75
+ <% end %>
76
+ </tbody>
77
+ </table>
78
+ </div>
@@ -0,0 +1,41 @@
1
+ <div class="card">
2
+ <h2>Referrals</h2>
3
+ <table class="table">
4
+ <thead>
5
+ <tr>
6
+ <th>Referral Code</th>
7
+ <th>Clicked At</th>
8
+ <th>User Agent</th>
9
+ <th>IP Address</th>
10
+ <th>Signed Up At</th>
11
+ <th>Referee</th>
12
+ </tr>
13
+ </thead>
14
+ <tbody>
15
+ <% @referrals.each do |referral| %>
16
+ <tr>
17
+ <td><code><%= referral.referral_code %></code></td>
18
+ <td><%= referral.clicked_at.strftime("%b %d, %Y %H:%M") %></td>
19
+ <td><%= truncate(referral.user_agent, length: 50) %></td>
20
+ <td><%= referral.ip_address %></td>
21
+ <td>
22
+ <% if referral.signed_up_at %>
23
+ <%= referral.signed_up_at.strftime("%b %d, %Y %H:%M") %>
24
+ <% else %>
25
+ <span class="text-muted">Not signed up</span>
26
+ <% end %>
27
+ </td>
28
+ <td>
29
+ <% if referral.referee %>
30
+ <%= referral.referee.email rescue referral.referee.id %>
31
+ <% else %>
32
+ <span class="text-muted">-</span>
33
+ <% end %>
34
+ </td>
35
+ </tr>
36
+ <% end %>
37
+ </tbody>
38
+ </table>
39
+
40
+ <%= loyalty_paginate @referrals %>
41
+ </div>
@@ -0,0 +1,31 @@
1
+ <div class="card">
2
+ <h2>Transactions</h2>
3
+ <table class="table">
4
+ <thead>
5
+ <tr>
6
+ <th>User</th>
7
+ <th>Type</th>
8
+ <th>Points</th>
9
+ <th>Date</th>
10
+ <th>Description</th>
11
+ </tr>
12
+ </thead>
13
+ <tbody>
14
+ <% @transactions.each do |transaction| %>
15
+ <tr>
16
+ <td><%= transaction.user.email rescue transaction.user.id %></td>
17
+ <td>
18
+ <span class="badge <%= transaction.transaction_type %>">
19
+ <%= transaction.transaction_type.titleize %>
20
+ </span>
21
+ </td>
22
+ <td><%= transaction.points %></td>
23
+ <td><%= transaction.created_at.strftime("%b %d, %Y") %></td>
24
+ <td><%= transaction.description %></td>
25
+ </tr>
26
+ <% end %>
27
+ </tbody>
28
+ </table>
29
+
30
+ <%= loyalty_paginate @transactions %>
31
+ </div>
@@ -0,0 +1,50 @@
1
+ <div class="card">
2
+ <h2>User Details: <%= @user.email %></h2>
3
+
4
+ <div class="stats">
5
+ <div class="stat">
6
+ <h3>Points Balance</h3>
7
+ <p class="big-number"><%= @balance %></p>
8
+ </div>
9
+
10
+ <div class="stat">
11
+ <h3>Current Tier</h3>
12
+ <p class="big-number"><%= @tier || 'None' %></p>
13
+ </div>
14
+
15
+ <div class="stat">
16
+ <h3>Referral Code</h3>
17
+ <p><code><%= @user.referral_code %></code></p>
18
+ </div>
19
+ </div>
20
+
21
+ <h3>Recent Transactions</h3>
22
+ <table class="table">
23
+ <thead>
24
+ <tr>
25
+ <th>Type</th>
26
+ <th>Points</th>
27
+ <th>Date</th>
28
+ <th>Description</th>
29
+ </tr>
30
+ </thead>
31
+ <tbody>
32
+ <% @transactions.each do |transaction| %>
33
+ <tr>
34
+ <td>
35
+ <span class="badge <%= transaction.transaction_type %>">
36
+ <%= transaction.transaction_type.titleize %>
37
+ </span>
38
+ </td>
39
+ <td><%= transaction.points %></td>
40
+ <td><%= transaction.created_at.strftime("%b %d, %Y") %></td>
41
+ <td><%= transaction.description %></td>
42
+ </tr>
43
+ <% end %>
44
+ </tbody>
45
+ </table>
46
+
47
+ <%= loyalty_paginate @transactions %>
48
+
49
+ <%= link_to 'Back to Users', loyalty_ref.users_path, class: 'btn btn-secondary' %>
50
+ </div>
@@ -0,0 +1,40 @@
1
+ <div class="card">
2
+ <h2>Users</h2>
3
+ <table class="table">
4
+ <thead>
5
+ <tr>
6
+ <th>User</th>
7
+ <th>Points Balance</th>
8
+ <th>Current Tier</th>
9
+ <th>Referral Code</th>
10
+ <th>Total Referrals</th>
11
+ <th>Joined</th>
12
+ <th>Actions</th>
13
+ </tr>
14
+ </thead>
15
+ <tbody>
16
+ <% @users.each do |user| %>
17
+ <tr>
18
+ <td><%= user.email rescue user.id %></td>
19
+ <td><strong><%= LoyaltyRef.balance(user) %></strong></td>
20
+ <td>
21
+ <% tier = LoyaltyRef.tier(user) %>
22
+ <% if tier %>
23
+ <span class="badge <%= tier.downcase %>"><%= tier %></span>
24
+ <% else %>
25
+ <span class="badge">None</span>
26
+ <% end %>
27
+ </td>
28
+ <td><code><%= user.referral_code %></code></td>
29
+ <td><%= user.total_referrals rescue 0 %></td>
30
+ <td><%= user.created_at.strftime("%b %d, %Y") %></td>
31
+ <td>
32
+ <%= link_to "View Details", loyalty_ref.user_path(user), class: "btn btn-primary" %>
33
+ </td>
34
+ </tr>
35
+ <% end %>
36
+ </tbody>
37
+ </table>
38
+
39
+ <%= loyalty_paginate @users %>
40
+ </div>
data/config/routes.rb ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ LoyaltyRef::Engine.routes.draw do
4
+ root to: "dashboard#index"
5
+
6
+ get "users", to: "dashboard#users"
7
+ get "users/:id", to: "dashboard#user", as: :user
8
+ get "transactions", to: "dashboard#transactions"
9
+ get "referrals", to: "dashboard#referrals"
10
+ get "analytics", to: "dashboard#analytics"
11
+
12
+ # Tracking endpoint for device data collection
13
+ post "track_referral", to: "tracking#track_referral", as: :track_referral
14
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateLoyaltyRefTransactions < ActiveRecord::Migration[7.0]
4
+ def change
5
+ create_table :loyalty_ref_transactions do |t|
6
+ t.references :user, polymorphic: true, null: false, index: true
7
+ t.integer :points, null: false
8
+ t.string :transaction_type, null: false
9
+ t.json :event_data
10
+ t.json :offer_data
11
+ t.datetime :expires_at
12
+ t.text :description
13
+
14
+ t.timestamps
15
+ end
16
+
17
+ add_index :loyalty_ref_transactions, :transaction_type
18
+ add_index :loyalty_ref_transactions, :expires_at
19
+ add_index :loyalty_ref_transactions, :created_at
20
+ end
21
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateLoyaltyRefReferralLogs < ActiveRecord::Migration[7.0]
4
+ def change
5
+ create_table :loyalty_ref_referral_logs do |t|
6
+ t.string :referral_code, null: false
7
+ t.references :referee, polymorphic: true, null: true
8
+ t.string :user_agent
9
+ t.string :ip_address
10
+ t.string :referrer
11
+ t.datetime :clicked_at, null: false
12
+ t.datetime :signed_up_at
13
+ t.string :device_type
14
+ t.string :browser
15
+ t.json :geo_data
16
+ t.json :device_data
17
+
18
+ t.timestamps
19
+ end
20
+
21
+ add_index :loyalty_ref_referral_logs, :referral_code
22
+ add_index :loyalty_ref_referral_logs, :clicked_at
23
+ add_index :loyalty_ref_referral_logs, :signed_up_at
24
+ add_index :loyalty_ref_referral_logs, :ip_address
25
+ add_index :loyalty_ref_referral_logs, :device_type
26
+ add_index :loyalty_ref_referral_logs, :browser
27
+ end
28
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module LoyaltyRef
6
+ module Generators
7
+ class InstallGenerator < Rails::Generators::Base
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ def create_initializer
11
+ template "initializer.rb", "config/initializers/loyalty_ref.rb"
12
+ end
13
+
14
+ def add_user_model_migration
15
+ generate_migration = ask("Would you like to add referral_code and tier columns to your User model? (y/n)")
16
+
17
+ if generate_migration.downcase == "y"
18
+ generate "migration", "AddLoyaltyRefToUsers referral_code:string tier:string"
19
+ end
20
+ end
21
+
22
+ def add_user_model_methods
23
+ user_class = ask("What is your user model class name? (default: User)")
24
+ user_class = "User" if user_class.blank?
25
+
26
+ template "user_model.rb", "app/models/#{user_class.downcase}.rb", force: true
27
+ end
28
+
29
+ def show_instructions
30
+ puts "\n" + "="*60
31
+ puts "🎉 LoyaltyRef has been installed successfully!"
32
+ puts "="*60
33
+ puts "\nNext steps:"
34
+ puts "1. Run: rails db:migrate"
35
+ puts "2. Customize config/initializers/loyalty_ref.rb"
36
+ puts "3. Access admin dashboard at: /loyalty"
37
+ puts "\nFor more information, visit: https://github.com/rails_to_rescue/loyalty_ref"
38
+ puts "="*60
39
+ end
40
+ end
41
+ end
42
+ end