referral_box 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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +27 -0
- data/LICENSE.txt +21 -0
- data/README.md +194 -0
- data/app/assets/javascripts/referral_box.js +138 -0
- data/app/controllers/referral_box/dashboard_controller.rb +91 -0
- data/app/controllers/referral_box/tracking_controller.rb +30 -0
- data/app/helpers/referral_box/application_helper.rb +56 -0
- data/app/helpers/referral_box/dashboard_helper.rb +41 -0
- data/app/views/layouts/referral_box/dashboard.html.erb +233 -0
- data/config/routes.rb +14 -0
- data/db/migrate/001_create_referral_box_transactions.rb +21 -0
- data/db/migrate/002_create_referral_box_referral_logs.rb +28 -0
- data/lib/generators/referral_box/install/install_generator.rb +45 -0
- data/lib/generators/referral_box/install/templates/initializer.rb +63 -0
- data/lib/generators/referral_box/install/templates/user_model.rb +45 -0
- data/lib/referral_box/configuration.rb +32 -0
- data/lib/referral_box/engine.rb +31 -0
- data/lib/referral_box/models/referral_log.rb +109 -0
- data/lib/referral_box/models/transaction.rb +39 -0
- data/lib/referral_box/version.rb +5 -0
- data/lib/referral_box.rb +191 -0
- metadata +233 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ee4006fbe4caff6e1727695dd94283356d36fcb405164d3845f9084c4c1ca581
|
4
|
+
data.tar.gz: 2f29cb7b61c05f1935581579d18bc79d79754485fa9b654c2f916db7398413e7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6bd9ff77d31909ff80e5d64e862ffd9ab41242cebb8a17e4ff4c1002d00a9a715a85ed9ffdae120a8537bbcf948fd21a53783aece4a5a1d02dbe88d2124d29c1
|
7
|
+
data.tar.gz: 0bb70dacb0c2261cbcd77194e75d2bccd51f897c148e0c72c6d291001e074687464b7f371408833e01f7945eab04b7b73883eba29987e26bbe901cea701e87cc
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
|
+
|
8
|
+
## [Unreleased]
|
9
|
+
|
10
|
+
## [0.1.0] - 2025-01-XX
|
11
|
+
|
12
|
+
### Added
|
13
|
+
- Initial release of ReferralBox gem
|
14
|
+
- Points system with customizable earning/redeem rules
|
15
|
+
- Dynamic tier levels (Silver, Gold, Platinum)
|
16
|
+
- Unique referral codes and multi-level tracking
|
17
|
+
- Built-in tracking for signups, geo, device (no Redis needed)
|
18
|
+
- Admin dashboard with ERB views
|
19
|
+
- Support for any user model (User, Customer, Account, etc.)
|
20
|
+
- Rails generator for easy installation
|
21
|
+
- Comprehensive configuration system
|
22
|
+
- Transaction logging and analytics
|
23
|
+
- Referral sharing with full analytics
|
24
|
+
- Points expiration system
|
25
|
+
- Tier change callbacks
|
26
|
+
- Device and browser detection
|
27
|
+
- Conversion rate tracking
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 Kapil Pal
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,194 @@
|
|
1
|
+
# ReferralBox 📦
|
2
|
+
|
3
|
+
A flexible Ruby gem for building loyalty and referral systems in Rails apps.
|
4
|
+
|
5
|
+
* 🎁 Reward users with points based on their activity
|
6
|
+
* 🧱 Create dynamic tier levels (Silver, Gold, Platinum)
|
7
|
+
* 🤝 Add referral rewards with unique referral codes
|
8
|
+
* 🔧 Fully customizable and configurable
|
9
|
+
* 🎛️ Admin dashboard (ERB based)
|
10
|
+
* 🔄 Supports any user model: `User`, `Customer`, `Account`, etc.
|
11
|
+
|
12
|
+
---
|
13
|
+
|
14
|
+
## 📚 Documentation
|
15
|
+
|
16
|
+
- **[Quick Start Guide](QUICK_START.md)** - Get running in 5 minutes
|
17
|
+
- **[Complete Documentation](DOCUMENTATION.md)** - Full developer guide with examples
|
18
|
+
- **[API Reference](DOCUMENTATION.md#api-reference)** - All methods and options
|
19
|
+
|
20
|
+
---
|
21
|
+
|
22
|
+
## 🚀 Installation
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
# Gemfile
|
26
|
+
gem 'referral_box'
|
27
|
+
```
|
28
|
+
|
29
|
+
```bash
|
30
|
+
$ bundle install
|
31
|
+
$ rails generate referral_box:install
|
32
|
+
$ rails db:migrate
|
33
|
+
```
|
34
|
+
|
35
|
+
---
|
36
|
+
|
37
|
+
## 🛠️ Configuration
|
38
|
+
|
39
|
+
Create your configuration block in an initializer:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
# config/initializers/referral_box.rb
|
43
|
+
|
44
|
+
ReferralBox.configure do |config|
|
45
|
+
# Define which model represents your app's user/customer
|
46
|
+
config.reference_class_name = 'User' # or 'Customer', 'Account', etc.
|
47
|
+
|
48
|
+
config.earning_rule = ->(user, event) do
|
49
|
+
# Example: earn 10 points per ₹100 spent
|
50
|
+
event.amount / 10
|
51
|
+
end
|
52
|
+
|
53
|
+
config.redeem_rule = ->(user, offer) do
|
54
|
+
offer.cost_in_points
|
55
|
+
end
|
56
|
+
|
57
|
+
config.tier_thresholds = {
|
58
|
+
"Silver" => 500,
|
59
|
+
"Gold" => 1000,
|
60
|
+
"Platinum" => 2500
|
61
|
+
}
|
62
|
+
|
63
|
+
config.reward_modifier = ->(user) do
|
64
|
+
case user.tier
|
65
|
+
when "Silver" then 1.0
|
66
|
+
when "Gold" then 1.2
|
67
|
+
when "Platinum" then 1.5
|
68
|
+
else 1.0
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
config.referral_reward = ->(referrer, referee) do
|
73
|
+
ReferralBox.earn_points(referrer, 100)
|
74
|
+
ReferralBox.earn_points(referee, 50)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
```
|
78
|
+
|
79
|
+
---
|
80
|
+
|
81
|
+
## ✅ Features
|
82
|
+
|
83
|
+
### 🎁 Loyalty Program
|
84
|
+
|
85
|
+
| Feature | Description |
|
86
|
+
| -------------------- | ----------------------------- |
|
87
|
+
| Points system | Earn points via config lambda |
|
88
|
+
| Custom earning rules | Define rules per event/user |
|
89
|
+
| Redeem points | Redeem points for offers |
|
90
|
+
| Manual adjustment | Admins can modify balances |
|
91
|
+
| Points expiration | e.g. 90 days |
|
92
|
+
| Transaction logging | All activity is logged |
|
93
|
+
| Check balance | Easy method to check |
|
94
|
+
|
95
|
+
### 🧱 Tier System (Dynamic)
|
96
|
+
|
97
|
+
| Feature | Description |
|
98
|
+
| ----------------------- | --------------------------- |
|
99
|
+
| Dynamic definitions | e.g. Silver => 500 points |
|
100
|
+
| Auto tier assignment | Based on balance |
|
101
|
+
| Callbacks on promotion | `on_tier_changed` hook |
|
102
|
+
| Reward modifier by tier | e.g. Gold users get +20% |
|
103
|
+
| DB persistence | Can store or calculate tier |
|
104
|
+
|
105
|
+
### 🤝 Referral System
|
106
|
+
|
107
|
+
| Feature | Description |
|
108
|
+
| --------------------- | ------------------------------------------------------------- |
|
109
|
+
| Unique referral codes | Auto-generated or custom |
|
110
|
+
| ?ref=code tracking | Via signup links |
|
111
|
+
| Multi-level referrals | Parent/child tree |
|
112
|
+
| Referral rewards | Custom logic supported |
|
113
|
+
| Referral analytics | Track clicks, accepted signups, geo-location, and device type |
|
114
|
+
|
115
|
+
### ⚙️ Core Gem Features
|
116
|
+
|
117
|
+
* Developer config block
|
118
|
+
* Extensible models
|
119
|
+
* Simple public API: `earn_points`, `redeem_points`, `balance`, `track_referral`
|
120
|
+
* Rails generators for setup
|
121
|
+
* Support for any user model (User, Account, Customer, etc.)
|
122
|
+
|
123
|
+
### 🖥️ Admin UI
|
124
|
+
|
125
|
+
* Mountable engine with ERB templates
|
126
|
+
* Routes like `/referral_box`
|
127
|
+
* Views to list users, transactions, referrals
|
128
|
+
|
129
|
+
---
|
130
|
+
|
131
|
+
## 🔮 Future Scope
|
132
|
+
|
133
|
+
### 📊 Analytics & Admin
|
134
|
+
|
135
|
+
* Leaderboards by points
|
136
|
+
* Referral tree visualizer
|
137
|
+
* ActiveAdmin / custom dashboard
|
138
|
+
* Export CSV/JSON of logs
|
139
|
+
|
140
|
+
### 🔔 Engagement
|
141
|
+
|
142
|
+
* Email / in-app notifications
|
143
|
+
* Badges based on milestones
|
144
|
+
* Activity calendar
|
145
|
+
* Social sharing for referral links
|
146
|
+
|
147
|
+
---
|
148
|
+
|
149
|
+
## 📂 Folder Structure (Gem)
|
150
|
+
|
151
|
+
```
|
152
|
+
lib/
|
153
|
+
├── referral_box.rb
|
154
|
+
├── referral_box/
|
155
|
+
│ ├── engine.rb
|
156
|
+
│ ├── configuration.rb
|
157
|
+
│ ├── version.rb
|
158
|
+
│ ├── models/
|
159
|
+
│ │ ├── transaction.rb
|
160
|
+
│ │ ├── referral_log.rb
|
161
|
+
│ └── controllers/
|
162
|
+
│ ├── dashboard_controller.rb
|
163
|
+
app/views/referral_box/dashboard/
|
164
|
+
├── index.html.erb
|
165
|
+
├── show.html.erb
|
166
|
+
```
|
167
|
+
|
168
|
+
---
|
169
|
+
|
170
|
+
## 🧪 Usage Examples
|
171
|
+
|
172
|
+
```ruby
|
173
|
+
# Earn points
|
174
|
+
ReferralBox.earn_points(current_user, event: order)
|
175
|
+
|
176
|
+
# Redeem points
|
177
|
+
ReferralBox.redeem_points(current_user, offer: coupon)
|
178
|
+
|
179
|
+
# Check balance
|
180
|
+
ReferralBox.balance(current_user)
|
181
|
+
|
182
|
+
# Track referral
|
183
|
+
ReferralBox.track_referral(ref_code: params[:ref])
|
184
|
+
```
|
185
|
+
|
186
|
+
---
|
187
|
+
|
188
|
+
## 📬 Contribution
|
189
|
+
|
190
|
+
PRs are welcome 🙌 — help improve the gem or suggest features.
|
191
|
+
|
192
|
+
## 📜 License
|
193
|
+
|
194
|
+
MIT © 2025 Kapil Pal
|
@@ -0,0 +1,138 @@
|
|
1
|
+
// ReferralBox - Device Detection and Geo-location Collection
|
2
|
+
// This script automatically collects device and location data when users click referral links
|
3
|
+
|
4
|
+
(function() {
|
5
|
+
'use strict';
|
6
|
+
|
7
|
+
// Check if ReferralBox tracking is enabled
|
8
|
+
if (typeof window.ReferralBoxConfig === 'undefined' || !window.ReferralBoxConfig.enabled) {
|
9
|
+
return;
|
10
|
+
}
|
11
|
+
|
12
|
+
// Device detection
|
13
|
+
function detectDevice() {
|
14
|
+
const userAgent = navigator.userAgent.toLowerCase();
|
15
|
+
|
16
|
+
// Device type detection
|
17
|
+
let deviceType = 'desktop';
|
18
|
+
if (/mobile|android|iphone|ipod|blackberry|windows phone/.test(userAgent)) {
|
19
|
+
deviceType = 'mobile';
|
20
|
+
} else if (/tablet|ipad/.test(userAgent)) {
|
21
|
+
deviceType = 'tablet';
|
22
|
+
}
|
23
|
+
|
24
|
+
// Browser detection
|
25
|
+
let browser = 'Other';
|
26
|
+
if (userAgent.includes('chrome')) {
|
27
|
+
browser = 'Chrome';
|
28
|
+
} else if (userAgent.includes('firefox')) {
|
29
|
+
browser = 'Firefox';
|
30
|
+
} else if (userAgent.includes('safari') && !userAgent.includes('chrome')) {
|
31
|
+
browser = 'Safari';
|
32
|
+
} else if (userAgent.includes('edge')) {
|
33
|
+
browser = 'Edge';
|
34
|
+
} else if (userAgent.includes('opera')) {
|
35
|
+
browser = 'Opera';
|
36
|
+
}
|
37
|
+
|
38
|
+
return {
|
39
|
+
device_type: deviceType,
|
40
|
+
browser: browser,
|
41
|
+
screen_width: screen.width,
|
42
|
+
screen_height: screen.height,
|
43
|
+
viewport_width: window.innerWidth,
|
44
|
+
viewport_height: window.innerHeight,
|
45
|
+
language: navigator.language,
|
46
|
+
platform: navigator.platform,
|
47
|
+
cookie_enabled: navigator.cookieEnabled,
|
48
|
+
online: navigator.onLine
|
49
|
+
};
|
50
|
+
}
|
51
|
+
|
52
|
+
// Geo-location detection (if enabled and user consents)
|
53
|
+
function detectLocation() {
|
54
|
+
return new Promise((resolve) => {
|
55
|
+
if (!window.ReferralBoxConfig.collect_geo || !navigator.geolocation) {
|
56
|
+
resolve(null);
|
57
|
+
return;
|
58
|
+
}
|
59
|
+
|
60
|
+
navigator.geolocation.getCurrentPosition(
|
61
|
+
function(position) {
|
62
|
+
resolve({
|
63
|
+
latitude: position.coords.latitude,
|
64
|
+
longitude: position.coords.longitude,
|
65
|
+
accuracy: position.coords.accuracy,
|
66
|
+
timestamp: position.timestamp
|
67
|
+
});
|
68
|
+
},
|
69
|
+
function(error) {
|
70
|
+
console.log('ReferralBox: Geolocation not available or denied');
|
71
|
+
resolve(null);
|
72
|
+
},
|
73
|
+
{
|
74
|
+
enableHighAccuracy: false,
|
75
|
+
timeout: 5000,
|
76
|
+
maximumAge: 60000
|
77
|
+
}
|
78
|
+
);
|
79
|
+
});
|
80
|
+
}
|
81
|
+
|
82
|
+
// Collect all device and location data
|
83
|
+
async function collectDeviceData() {
|
84
|
+
const deviceData = detectDevice();
|
85
|
+
const locationData = await detectLocation();
|
86
|
+
|
87
|
+
return {
|
88
|
+
...deviceData,
|
89
|
+
geo_data: locationData,
|
90
|
+
collected_at: new Date().toISOString()
|
91
|
+
};
|
92
|
+
}
|
93
|
+
|
94
|
+
// Enhanced referral tracking
|
95
|
+
function enhanceReferralTracking() {
|
96
|
+
// Find all referral links
|
97
|
+
const referralLinks = document.querySelectorAll('a[href*="?ref="], a[href*="&ref="]');
|
98
|
+
|
99
|
+
referralLinks.forEach(link => {
|
100
|
+
link.addEventListener('click', async function(e) {
|
101
|
+
// Only enhance if it's a referral link
|
102
|
+
if (!this.href.includes('ref=')) return;
|
103
|
+
|
104
|
+
try {
|
105
|
+
// Collect device and location data
|
106
|
+
const deviceData = await collectDeviceData();
|
107
|
+
|
108
|
+
// Store in sessionStorage for the next page load
|
109
|
+
sessionStorage.setItem('referral_box_device_data', JSON.stringify(deviceData));
|
110
|
+
|
111
|
+
// Add a small delay to ensure data is stored
|
112
|
+
setTimeout(() => {
|
113
|
+
// Continue with normal link navigation
|
114
|
+
return true;
|
115
|
+
}, 100);
|
116
|
+
|
117
|
+
} catch (error) {
|
118
|
+
console.error('ReferralBox: Error collecting device data:', error);
|
119
|
+
}
|
120
|
+
});
|
121
|
+
});
|
122
|
+
}
|
123
|
+
|
124
|
+
// Initialize when DOM is ready
|
125
|
+
if (document.readyState === 'loading') {
|
126
|
+
document.addEventListener('DOMContentLoaded', enhanceReferralTracking);
|
127
|
+
} else {
|
128
|
+
enhanceReferralTracking();
|
129
|
+
}
|
130
|
+
|
131
|
+
// Make functions available globally for manual use
|
132
|
+
window.ReferralBox = {
|
133
|
+
detectDevice: detectDevice,
|
134
|
+
detectLocation: detectLocation,
|
135
|
+
collectDeviceData: collectDeviceData
|
136
|
+
};
|
137
|
+
|
138
|
+
})();
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ReferralBox
|
4
|
+
class DashboardController < ActionController::Base
|
5
|
+
layout "referral_box/dashboard"
|
6
|
+
|
7
|
+
before_action :authenticate_user!
|
8
|
+
before_action :ensure_admin_access!
|
9
|
+
|
10
|
+
def index
|
11
|
+
@total_users = user_class.count
|
12
|
+
@total_transactions = ReferralBox::Transaction.count
|
13
|
+
@total_referrals = ReferralBox::ReferralLog.count
|
14
|
+
@conversion_rate = ReferralBox::ReferralLog.conversion_rate
|
15
|
+
|
16
|
+
@recent_transactions = ReferralBox::Transaction.includes(:user)
|
17
|
+
.order(created_at: :desc)
|
18
|
+
.limit(10)
|
19
|
+
|
20
|
+
@top_users = user_class.joins(:referral_box_transactions)
|
21
|
+
.group("users.id")
|
22
|
+
.order("SUM(referral_box_transactions.points) DESC")
|
23
|
+
.limit(10)
|
24
|
+
end
|
25
|
+
|
26
|
+
def users
|
27
|
+
@users = safe_paginate(user_class.includes(:referral_box_transactions)
|
28
|
+
.order(created_at: :desc), 20)
|
29
|
+
end
|
30
|
+
|
31
|
+
def user
|
32
|
+
@user = user_class.find(params[:id])
|
33
|
+
@transactions = safe_paginate(@user.referral_box_transactions
|
34
|
+
.order(created_at: :desc), 20)
|
35
|
+
@balance = ReferralBox.balance(@user)
|
36
|
+
@tier = ReferralBox.tier(@user)
|
37
|
+
end
|
38
|
+
|
39
|
+
def transactions
|
40
|
+
@transactions = safe_paginate(ReferralBox::Transaction.includes(:user)
|
41
|
+
.order(created_at: :desc), 50)
|
42
|
+
end
|
43
|
+
|
44
|
+
def referrals
|
45
|
+
@referrals = safe_paginate(ReferralBox::ReferralLog.order(clicked_at: :desc), 50)
|
46
|
+
end
|
47
|
+
|
48
|
+
def analytics
|
49
|
+
@total_clicks = ReferralBox::ReferralLog.clicked_count
|
50
|
+
@total_conversions = ReferralBox::ReferralLog.converted_count
|
51
|
+
@conversion_rate = ReferralBox::ReferralLog.conversion_rate
|
52
|
+
|
53
|
+
@referrals_by_device = ReferralBox::ReferralLog.group(:device_type).count
|
54
|
+
@referrals_by_browser = ReferralBox::ReferralLog.group(:browser).count
|
55
|
+
|
56
|
+
@daily_clicks = ReferralBox::ReferralLog.group("DATE(clicked_at)")
|
57
|
+
.count
|
58
|
+
.sort_by { |date, _| date }
|
59
|
+
.last(30)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def user_class
|
65
|
+
ReferralBox.configuration.reference_class_name.constantize
|
66
|
+
end
|
67
|
+
|
68
|
+
def safe_paginate(relation, per_page = 20)
|
69
|
+
if defined?(Kaminari) && relation.respond_to?(:page)
|
70
|
+
relation.page(params[:page]).per(per_page)
|
71
|
+
else
|
72
|
+
# Fallback pagination without Kaminari
|
73
|
+
page = (params[:page] || 1).to_i
|
74
|
+
offset = (page - 1) * per_page
|
75
|
+
relation.offset(offset).limit(per_page)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def authenticate_user!
|
80
|
+
# Override this method in your app to implement authentication
|
81
|
+
# For example: redirect_to login_path unless current_user
|
82
|
+
true
|
83
|
+
end
|
84
|
+
|
85
|
+
def ensure_admin_access!
|
86
|
+
# Override this method in your app to implement admin authorization
|
87
|
+
# For example: redirect_to root_path unless current_user&.admin?
|
88
|
+
true
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ReferralBox
|
4
|
+
class TrackingController < ActionController::Base
|
5
|
+
skip_before_action :verify_authenticity_token, only: [:track_referral]
|
6
|
+
|
7
|
+
def track_referral
|
8
|
+
ref_code = params[:ref_code]
|
9
|
+
device_data = params[:device_data]
|
10
|
+
|
11
|
+
if ref_code.present?
|
12
|
+
success = ReferralBox.track_referral(
|
13
|
+
ref_code: ref_code,
|
14
|
+
user_agent: request.user_agent,
|
15
|
+
ip_address: request.remote_ip,
|
16
|
+
referrer: request.referer,
|
17
|
+
device_data: device_data
|
18
|
+
)
|
19
|
+
|
20
|
+
if success
|
21
|
+
render json: { success: true, message: "Referral tracked successfully" }
|
22
|
+
else
|
23
|
+
render json: { success: false, message: "Failed to track referral" }, status: 422
|
24
|
+
end
|
25
|
+
else
|
26
|
+
render json: { success: false, message: "Referral code is required" }, status: 400
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ReferralBox
|
4
|
+
module ApplicationHelper
|
5
|
+
# Include ReferralBox JavaScript and configuration
|
6
|
+
def referral_box_javascript_tag
|
7
|
+
return unless ReferralBox.configuration.enable_device_tracking
|
8
|
+
|
9
|
+
config = {
|
10
|
+
enabled: ReferralBox.configuration.enable_device_tracking,
|
11
|
+
collect_geo: ReferralBox.configuration.collect_geo_location,
|
12
|
+
api_endpoint: referral_box_track_referral_path
|
13
|
+
}
|
14
|
+
|
15
|
+
content_tag(:script, "window.ReferralBoxConfig = #{config.to_json};", type: 'application/javascript') +
|
16
|
+
javascript_include_tag('referral_box', 'data-turbolinks-track': 'reload')
|
17
|
+
end
|
18
|
+
|
19
|
+
# Generate a referral link with enhanced tracking
|
20
|
+
def referral_box_referral_link(user, options = {})
|
21
|
+
return unless user.respond_to?(:referral_code) && user.referral_code.present?
|
22
|
+
|
23
|
+
base_url = options[:base_url] || root_url
|
24
|
+
ref_param = options[:ref_param] || 'ref'
|
25
|
+
|
26
|
+
"#{base_url}#{base_url.include?('?') ? '&' : '?'}#{ref_param}=#{user.referral_code}"
|
27
|
+
end
|
28
|
+
|
29
|
+
# Generate a referral link button with styling
|
30
|
+
def referral_box_referral_button(user, text = nil, options = {})
|
31
|
+
text ||= "Share Referral Link"
|
32
|
+
link_text = options[:icon] ? "#{options[:icon]} #{text}" : text
|
33
|
+
|
34
|
+
link_to(
|
35
|
+
link_text,
|
36
|
+
referral_box_referral_link(user, options),
|
37
|
+
options.merge(
|
38
|
+
class: "referral-box-referral-link #{options[:class]}".strip,
|
39
|
+
target: options[:target] || '_blank',
|
40
|
+
rel: options[:rel] || 'noopener'
|
41
|
+
)
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Display user's referral stats
|
46
|
+
def referral_box_user_stats(user)
|
47
|
+
return unless user.respond_to?(:points_balance)
|
48
|
+
|
49
|
+
content_tag(:div, class: 'referral-box-user-stats') do
|
50
|
+
concat(content_tag(:div, "Points: #{user.points_balance}", class: 'points'))
|
51
|
+
concat(content_tag(:div, "Tier: #{user.current_tier || 'None'}", class: 'tier'))
|
52
|
+
concat(content_tag(:div, "Referrals: #{user.total_referrals rescue 0}", class: 'referrals'))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ReferralBox
|
4
|
+
module DashboardHelper
|
5
|
+
def referral_box_paginate(collection)
|
6
|
+
if defined?(Kaminari) && collection.respond_to?(:current_page)
|
7
|
+
paginate(collection)
|
8
|
+
else
|
9
|
+
# Simple pagination without Kaminari
|
10
|
+
render_simple_pagination(collection)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def render_simple_pagination(collection)
|
17
|
+
return unless collection.respond_to?(:limit_value) && collection.respond_to?(:offset_value)
|
18
|
+
|
19
|
+
current_page = (collection.offset_value / collection.limit_value) + 1
|
20
|
+
|
21
|
+
# For fallback pagination, we need to get total count differently
|
22
|
+
# Since we don't have total_count method, we'll just show basic navigation
|
23
|
+
content_tag(:div, class: 'pagination') do
|
24
|
+
links = []
|
25
|
+
|
26
|
+
# Previous page
|
27
|
+
if current_page > 1
|
28
|
+
links << link_to('Previous', url_for(page: current_page - 1), class: 'pagination-link')
|
29
|
+
end
|
30
|
+
|
31
|
+
# Current page
|
32
|
+
links << content_tag(:span, "Page #{current_page}", class: 'pagination-current')
|
33
|
+
|
34
|
+
# Next page (we'll always show this for fallback)
|
35
|
+
links << link_to('Next', url_for(page: current_page + 1), class: 'pagination-link')
|
36
|
+
|
37
|
+
links.join.html_safe
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|