Compare commits
84 Commits
5645-payme
...
develop
| Author | SHA1 | Date |
|---|---|---|
|
|
f6ca42d018 | |
|
|
f3cdc28d8b | |
|
|
2eb24f2711 | |
|
|
db261a3df9 | |
|
|
03313eb418 | |
|
|
ebcf9d91aa | |
|
|
dfe5facad6 | |
|
|
abae34c108 | |
|
|
df8cf11e37 | |
|
|
7baf003b90 | |
|
|
1944602b97 | |
|
|
86e151d239 | |
|
|
6a1506bbe8 | |
|
|
64a93dd42f | |
|
|
eb52813822 | |
|
|
8613a03d00 | |
|
|
8f0b8929ba | |
|
|
08e1c5274d | |
|
|
5f347ccfac | |
|
|
ca2bf19b7a | |
|
|
fad1f9d6d9 | |
|
|
51838fb413 | |
|
|
45cb401112 | |
|
|
9078515984 | |
|
|
52ae83e2f4 | |
|
|
b454cf9ead | |
|
|
65f5624ff9 | |
|
|
e82450dfe9 | |
|
|
f1992eaa78 | |
|
|
bdeecc76c6 | |
|
|
8c8024c12b | |
|
|
c183af3d6b | |
|
|
ab424c21d2 | |
|
|
753f35b24d | |
|
|
624853c868 | |
|
|
71d8571eb9 | |
|
|
fae16c7483 | |
|
|
253a6c566b | |
|
|
ed561be4b5 | |
|
|
eb69640667 | |
|
|
9b0a9d1f32 | |
|
|
e1cc5483a2 | |
|
|
73d2a7a020 | |
|
|
eb298d6859 | |
|
|
93c4154648 | |
|
|
fbd871d204 | |
|
|
0d82f6ee16 | |
|
|
1279b16ec0 | |
|
|
e3cff0a825 | |
|
|
6a6e4cde09 | |
|
|
1dd15fb0aa | |
|
|
9282369e54 | |
|
|
828191d683 | |
|
|
c0031cfe3d | |
|
|
9b17546082 | |
|
|
eed3d51f4b | |
|
|
4eac68b645 | |
|
|
fe6157e8cf | |
|
|
675bf2b69c | |
|
|
4ffc0d9b3b | |
|
|
7b665325f7 | |
|
|
eab17b3340 | |
|
|
4fafe30141 | |
|
|
7f85c91601 | |
|
|
d424026f17 | |
|
|
a4f8935b3a | |
|
|
4f837ae67f | |
|
|
0053775c7e | |
|
|
9d6c71829f | |
|
|
d00a0c08f7 | |
|
|
7e2c917ca0 | |
|
|
a84a55f178 | |
|
|
25ecab2c65 | |
|
|
b2f344fd30 | |
|
|
3bba9ec619 | |
|
|
86e03e0ba7 | |
|
|
67fd15c75c | |
|
|
b7a41c6465 | |
|
|
a8d5b8e735 | |
|
|
f977b7298e | |
|
|
9a41e8a236 | |
|
|
3d113e3877 | |
|
|
c7e80a0694 | |
|
|
9058c8af1d |
|
|
@ -0,0 +1,23 @@
|
|||
name: CI Pipeline
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and Publish Admin
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: daggerdev/dagger:latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
run: |
|
||||
# Native checkout without node.js
|
||||
git clone http://gitea.jam-cloud-infra.svc.cluster.local/seth/jam-cloud.git .
|
||||
git checkout ${{ gitea.sha }}
|
||||
|
||||
- name: Login to Gitea Registry
|
||||
run: echo "${{ gitea.token }}" | docker login git.staging.jamkazam.com -u ${{ gitea.actor }} --password-stdin
|
||||
|
||||
- name: Build and Publish Admin
|
||||
working-directory: ./admin
|
||||
run: |
|
||||
dagger call build-local --source=. --repo-root=../ publish --address=git.staging.jamkazam.com/seth/jam-cloud-admin:latest
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
name: Environment Orchestrator
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
orchestrate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Component Deployment Gatekeeper
|
||||
run: |
|
||||
# JAM_CLUSTER_ENV should be set to 'staging' or 'production' in the Gitea Runner
|
||||
ENV="${JAM_CLUSTER_ENV:-staging}"
|
||||
echo "🌐 Cluster Environment: $ENV"
|
||||
|
||||
# 1. Extract modes for this environment
|
||||
ADMIN_MODE=$(jq -r ".environments.$ENV.admin" .jk-deploy.json)
|
||||
WEB_MODE=$(jq -r ".environments.$ENV.web" .jk-deploy.json)
|
||||
WS_MODE=$(jq -r ".environments.$ENV.[\"websocket-gateway\"]" .jk-deploy.json)
|
||||
|
||||
# 2. Conditional Execution
|
||||
if [ "$ADMIN_MODE" == "short-circuit" ]; then
|
||||
echo "⚡ ADMIN: Short-circuit detected. Deploying immediately..."
|
||||
cd admin && dagger call ship --source=.
|
||||
else
|
||||
echo "⏸️ ADMIN: Mode is $ADMIN_MODE. Skipping short-circuit deploy."
|
||||
fi
|
||||
|
||||
if [ "$WEB_MODE" == "short-circuit" ]; then
|
||||
echo "⚡ WEB: Short-circuit detected. Deploying immediately..."
|
||||
cd web && dagger call ship --source=.
|
||||
else
|
||||
echo "⏸️ WEB: Mode is $WEB_MODE. Skipping short-circuit deploy."
|
||||
fi
|
||||
|
||||
if [ "$WS_MODE" == "short-circuit" ]; then
|
||||
echo "⚡ WS-GATEWAY: Short-circuit detected. Deploying immediately..."
|
||||
cd websocket-gateway && just ship
|
||||
else
|
||||
echo "⏸️ WS-GATEWAY: Mode is $WS_MODE. Skipping short-circuit deploy."
|
||||
fi
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
name: Test Runner
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo "Runner is working!"
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
class Spacer
|
||||
def self.spacer(val, row)
|
||||
|
||||
percentage = ((val * 100) / row.total.to_f).round(1).to_s
|
||||
('%-5.5s' % percentage).gsub(' ', ' ') + '% - ' + val.to_s
|
||||
end
|
||||
end
|
||||
=begin
|
||||
select
|
||||
count(id) as total,
|
||||
count(first_downloaded_client_at) as downloaded,
|
||||
count(first_ran_client_at) as ran_client,
|
||||
count(first_certified_gear_at) as ftue,
|
||||
count(first_music_session_at) as any_session,
|
||||
count(first_real_music_session_at) as real_session,
|
||||
count(first_good_music_session_at) as good_session,
|
||||
count(first_invited_at) as invited,
|
||||
count(first_friended_at) as friended,
|
||||
count(first_subscribed_at) as subscribed
|
||||
from users where users.created_at >= '2024-11-01' AND users.created_at < '2025-04-01'
|
||||
|
||||
select first_name, last_name, email
|
||||
from users where users.created_at >= '2024-11-01' AND users.created_at < '2025-04-01'
|
||||
AND first_music_session_at is NULL;
|
||||
|
||||
=end
|
||||
|
||||
ActiveAdmin.register_page "Jammers Subscription Cohorts" do
|
||||
menu :parent => 'Reports'
|
||||
|
||||
content :title => "Jammers Subscription Cohorts" do
|
||||
|
||||
filter_type = params[:filter_type] || 'All'
|
||||
filter_campaign = params[:filter_campaign]
|
||||
filter_campaign_id = params[:filter_campaign_id]
|
||||
filter_ad_set = params[:filter_ad_set]
|
||||
filter_ad_name = params[:filter_ad_name]
|
||||
|
||||
campaigns = User.where("origin_utm_medium = 'cpc'").distinct.pluck(:origin_utm_campaign).compact.sort
|
||||
campaign_ids = User.where("origin_utm_medium = 'cpc'").distinct.pluck(:origin_id).compact.sort
|
||||
ad_sets = User.where("origin_utm_medium = 'cpc'").distinct.pluck(:origin_term).compact.sort
|
||||
ad_names = User.where("origin_utm_medium = 'cpc'").distinct.pluck(:origin_content).compact.sort
|
||||
|
||||
div style: "margin-bottom: 20px; padding: 10px; background-color: #f4f4f4; border-radius: 4px;" do
|
||||
form action: admin_jammers_subscription_cohorts_path, method: :get do
|
||||
span "Source: ", style: "font-weight: bold; margin-right: 5px;"
|
||||
select name: 'filter_type', onchange: 'this.form.submit()', style: "margin-right: 15px;" do
|
||||
option "All", value: 'All', selected: filter_type == 'All'
|
||||
option "Organic", value: 'Organic', selected: filter_type == 'Organic'
|
||||
option "Advertising", value: 'Advertising', selected: filter_type == 'Advertising'
|
||||
end
|
||||
|
||||
if filter_type == 'Advertising'
|
||||
div style: "margin-top: 10px;" do
|
||||
span "Campaign Name: ", style: "font-weight: bold; margin-right: 5px;"
|
||||
select name: 'filter_campaign', onchange: 'this.form.submit()', style: "margin-right: 15px;" do
|
||||
option "All", value: ''
|
||||
option "Null", value: 'NULL', selected: filter_campaign == 'NULL'
|
||||
campaigns.each do |c|
|
||||
option c, value: c, selected: filter_campaign == c
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
div style: "margin-top: 10px;" do
|
||||
span "Campaign ID: ", style: "font-weight: bold; margin-right: 5px;"
|
||||
select name: 'filter_campaign_id', onchange: 'this.form.submit()', style: "margin-right: 15px;" do
|
||||
option "All", value: ''
|
||||
option "Null", value: 'NULL', selected: filter_campaign_id == 'NULL'
|
||||
campaign_ids.each do |c|
|
||||
option c, value: c, selected: filter_campaign_id == c
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
div style: "margin-top: 10px;" do
|
||||
span "Ad Set: ", style: "font-weight: bold; margin-right: 5px;"
|
||||
select name: 'filter_ad_set', onchange: 'this.form.submit()', style: "margin-right: 15px;" do
|
||||
option "All", value: ''
|
||||
option "Null", value: 'NULL', selected: filter_ad_set == 'NULL'
|
||||
ad_sets.each do |c|
|
||||
option c, value: c, selected: filter_ad_set == c
|
||||
end
|
||||
end
|
||||
|
||||
div style: "margin-top: 10px;" do
|
||||
span "Ad Name: ", style: "font-weight: bold; margin-right: 5px;"
|
||||
select name: 'filter_ad_name', onchange: 'this.form.submit()', style: "margin-right: 15px;" do
|
||||
option "All", value: ''
|
||||
option "Null", value: 'NULL', selected: filter_ad_name == 'NULL'
|
||||
ad_names.each do |c|
|
||||
option c, value: c, selected: filter_ad_name == c
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
noscript { input type: :submit, value: "Filter" }
|
||||
end
|
||||
end
|
||||
|
||||
h2 "Users Grouped By Month as Paying Subscribers"
|
||||
|
||||
query = User.select(%Q{date_trunc('month', users.created_at) as month,
|
||||
count(id) as total,
|
||||
count(first_downloaded_client_at) as downloaded,
|
||||
count(first_ran_client_at) as ran_client,
|
||||
count(first_certified_gear_at) as ftue,
|
||||
count(first_music_session_at) as any_session,
|
||||
count(first_real_music_session_at) as real_session,
|
||||
count(first_good_music_session_at) as good_session,
|
||||
count(first_invited_at) as invited,
|
||||
count(first_friended_at) as friended,
|
||||
count(first_subscribed_at) as subscribed,
|
||||
count(first_played_jamtrack_at) as played_jamtrack
|
||||
})
|
||||
.joins(%Q{LEFT JOIN LATERAL (
|
||||
SELECT
|
||||
j.created_at
|
||||
FROM
|
||||
jam_track_rights as j
|
||||
WHERE
|
||||
j.user_id = users.id
|
||||
ORDER BY
|
||||
j.created_at
|
||||
LIMIT 1 -- Select only that single row
|
||||
) j ON TRUE })
|
||||
|
||||
if filter_type == 'Organic'
|
||||
query = query.where("users.origin_utm_source = 'organic'")
|
||||
elsif filter_type == 'Advertising'
|
||||
query = query.where("origin_utm_medium = 'cpc'")
|
||||
|
||||
if filter_campaign.present?
|
||||
if filter_campaign == 'NULL'
|
||||
query = query.where("users.origin_utm_campaign IS NULL")
|
||||
elsif filter_campaign != 'All'
|
||||
query = query.where("users.origin_utm_campaign = ?", filter_campaign)
|
||||
end
|
||||
end
|
||||
|
||||
if filter_campaign_id.present?
|
||||
if filter_campaign_id == 'NULL'
|
||||
query = query.where("users.origin_id IS NULL")
|
||||
elsif filter_campaign_id != 'All'
|
||||
query = query.where("users.origin_id = ?", filter_campaign_id)
|
||||
end
|
||||
end
|
||||
|
||||
if filter_ad_set.present?
|
||||
if filter_ad_set == 'NULL'
|
||||
query = query.where("users.origin_term IS NULL")
|
||||
elsif filter_ad_set != 'All'
|
||||
query = query.where("users.origin_term = ?", filter_ad_set)
|
||||
end
|
||||
end
|
||||
|
||||
if filter_ad_name.present?
|
||||
if filter_ad_name == 'NULL'
|
||||
query = query.where("users.origin_content IS NULL")
|
||||
elsif filter_ad_name != 'All'
|
||||
query = query.where("users.origin_content = ?", filter_ad_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
table_for query.group("date_trunc('month', users.created_at)")
|
||||
.where("j.created_at IS NULL OR (j.created_at - users.created_at) >= INTERVAL '2 hours'")
|
||||
.order("date_trunc('month', users.created_at) DESC") do |row|
|
||||
column "Month", Proc.new { |user| user.month.strftime('%B %Y') }
|
||||
column "Total", :total
|
||||
column "Subscribed", Proc.new { |user| raw(Spacer.spacer(user.subscribed, user)) }
|
||||
column "Downloaded", Proc.new { |user| raw(Spacer.spacer(user.downloaded, user)) }
|
||||
column "Ran Client", Proc.new { |user| raw(Spacer.spacer(user.ran_client, user)) }
|
||||
column "FTUE", Proc.new { |user| raw(Spacer.spacer(user.ftue, user)) }
|
||||
column "Any Session", Proc.new { |user| raw(Spacer.spacer(user.any_session, user)) }
|
||||
column "2+ Session", Proc.new { |user| raw(Spacer.spacer(user.real_session, user)) }
|
||||
column "Good Session", Proc.new { |user| raw(Spacer.spacer(user.good_session, user)) }
|
||||
column "Invited", Proc.new { |user| raw(Spacer.spacer(user.invited, user)) }
|
||||
column "Friended", Proc.new { |user| raw(Spacer.spacer(user.friended, user)) }
|
||||
column "Played JT", Proc.new { |user| raw(Spacer.spacer(user.played_jamtrack, user)) }
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -25,13 +25,12 @@ from users where users.created_at >= '2024-11-01' AND users.created_at < '2025-0
|
|||
|
||||
=end
|
||||
|
||||
ActiveAdmin.register_page "Subscription Cohorts" do
|
||||
ActiveAdmin.register_page "JamTrack Subscription Cohorts" do
|
||||
menu :parent => 'Reports'
|
||||
|
||||
|
||||
content :title => "Subscription Cohorts" do
|
||||
content :title => "JamTrack Subscription Cohorts" do
|
||||
h2 "Users Grouped By Month as Paying Subscribers"
|
||||
table_for User.select(%Q{date_trunc('month', created_at) as month,
|
||||
table_for User.select(%Q{date_trunc('month', users.created_at) as month,
|
||||
count(id) as total,
|
||||
count(first_downloaded_client_at) as downloaded,
|
||||
count(first_ran_client_at) as ran_client,
|
||||
|
|
@ -41,8 +40,21 @@ ActiveAdmin.register_page "Subscription Cohorts" do
|
|||
count(first_good_music_session_at) as good_session,
|
||||
count(first_invited_at) as invited,
|
||||
count(first_friended_at) as friended,
|
||||
count(first_subscribed_at) as subscribed
|
||||
}).group("date_trunc('month', created_at)").order("date_trunc('month', created_at) DESC") do |row|
|
||||
count(first_subscribed_at) as subscribed,
|
||||
count(first_played_jamtrack_at) as played_jamtrack
|
||||
})
|
||||
.joins(%Q{INNER JOIN LATERAL (
|
||||
SELECT
|
||||
j.created_at
|
||||
FROM
|
||||
jam_track_rights as j
|
||||
WHERE
|
||||
j.user_id = users.id
|
||||
ORDER BY
|
||||
j.created_at
|
||||
LIMIT 1 -- Select only that single row
|
||||
) j ON (j.created_at - users.created_at) < INTERVAL '2 hours' })
|
||||
.group("date_trunc('month', users.created_at)").order("date_trunc('month', users.created_at) DESC") do |row|
|
||||
column "Month", Proc.new { |user| user.month.strftime('%B %Y') }
|
||||
column "Total", :total
|
||||
column "Subscribed", Proc.new { |user| raw(Spacer.spacer(user.subscribed, user)) }
|
||||
|
|
@ -54,6 +66,7 @@ ActiveAdmin.register_page "Subscription Cohorts" do
|
|||
column "Good Session", Proc.new { |user| raw(Spacer.spacer(user.good_session, user)) }
|
||||
column "Invited", Proc.new { |user| raw(Spacer.spacer(user.invited, user)) }
|
||||
column "Friended", Proc.new { |user| raw(Spacer.spacer(user.friended, user)) }
|
||||
column "Played JT", Proc.new { |user| raw(Spacer.spacer(user.played_jamtrack, user)) }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -6,15 +6,18 @@ ActiveAdmin.register JamRuby::User, :as => 'UserSource' do
|
|||
config.batch_actions = false
|
||||
config.clear_action_items!
|
||||
config.filters = false
|
||||
config.per_page = 250
|
||||
|
||||
scope("Most Recent First", default: true) { |scope| scope.unscoped.order('created_at desc')}
|
||||
scope("Paid", default: true) { |scope| scope.unscoped.where(:origin_utm_medium => 'cpc').order('created_at desc') }
|
||||
scope("Inorganic Source") { |scope| scope.unscoped.where("origin_utm_source != 'organic' OR origin_utm_source IS NULL").order('created_at desc') }
|
||||
scope("Include Organic") { |scope| scope.unscoped.order('created_at desc') }
|
||||
|
||||
index do
|
||||
column "Email" do |user|
|
||||
user.email
|
||||
end
|
||||
column "Bought TestDrive" do |user|
|
||||
!user.most_recent_test_drive_purchase.nil? ? "Yes" : "No"
|
||||
column "Signup (CST)" do |user|
|
||||
user.created_at.in_time_zone("Central Time (US & Canada)")
|
||||
end
|
||||
column "UTM Source" do |user|
|
||||
user.origin_utm_source
|
||||
|
|
@ -25,8 +28,23 @@ ActiveAdmin.register JamRuby::User, :as => 'UserSource' do
|
|||
column "UTM Campaign" do |user|
|
||||
user.origin_utm_campaign
|
||||
end
|
||||
column "UTM ID" do |user|
|
||||
user.origin_id
|
||||
end
|
||||
column "UTM Term" do |user|
|
||||
user.origin_term
|
||||
end
|
||||
column "UTM Content" do |user|
|
||||
user.origin_content
|
||||
end
|
||||
column "Referrer" do |user|
|
||||
user.origin_referrer
|
||||
end
|
||||
column "FB Click ID" do |user|
|
||||
user.facebook_click_id
|
||||
end
|
||||
column "FB Browser ID" do |user|
|
||||
user.facebook_browser_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,55 +2,97 @@ class ArsesController < ApplicationController
|
|||
|
||||
respond_to :json
|
||||
|
||||
# create or update a client_artifact row
|
||||
def get_or_create
|
||||
name = params[:name]
|
||||
provider = params[:provider]
|
||||
active = params[:active]
|
||||
ip = params[:ip]
|
||||
username = params[:username]
|
||||
password = params[:password]
|
||||
topology = params[:topology]
|
||||
ars_id = params[:ars_id]
|
||||
puts "TOPOLOGY #{topology}"
|
||||
|
||||
if ars_id
|
||||
ars = Ars.find_by_id_int(ars_id)
|
||||
end
|
||||
if ars.nil?
|
||||
ars = Ars.new
|
||||
ars.name = name
|
||||
ars.id_int = ars_id if !ars_id.nil?
|
||||
def index
|
||||
if params[:code] != Rails.application.config.data_dump_code
|
||||
render :json => {error: "Unauthorized"}, :status => 401
|
||||
return
|
||||
end
|
||||
|
||||
ars.provider = provider
|
||||
ars.active = active
|
||||
ars.ip = ip
|
||||
ars.password = password
|
||||
ars.username = username
|
||||
if topology
|
||||
ars.city = topology['city']
|
||||
ars.country = topology['country']
|
||||
ars.continent = topology['continent']
|
||||
ars.latitude = topology['latitude']
|
||||
ars.longitude = topology['longitude']
|
||||
ars.subdivision = topology['subdivision']
|
||||
end
|
||||
ars.save
|
||||
@arses = JamRuby::Ars.all
|
||||
render :json => @arses
|
||||
end
|
||||
|
||||
@ars = ars
|
||||
unless @ars.errors.any?
|
||||
if ars_id.nil?
|
||||
ars.reload
|
||||
ars_id = ars.id_int
|
||||
def update
|
||||
if params[:code] != Rails.application.config.data_dump_code
|
||||
render :json => {error: "Unauthorized"}, :status => 401
|
||||
return
|
||||
end
|
||||
|
||||
begin
|
||||
# Primary ID lookup
|
||||
@ars = JamRuby::Ars.find_by_id(params[:id])
|
||||
|
||||
# Explicit secondary lookups if primary ID fails
|
||||
@ars ||= JamRuby::Ars.find_by_id_int(params[:id_int]) if params[:id_int]
|
||||
@ars ||= JamRuby::Ars.find_by_name(params[:name]) if params[:name]
|
||||
|
||||
if @ars.nil?
|
||||
render :json => {error: "Not Found"}, :status => 404
|
||||
return
|
||||
end
|
||||
|
||||
@ars = Ars.find_by_id_int(ars_id)
|
||||
render :json => {id_int: @ars.id_int, id: @ars.id, name: @ars.name, provider: @ars.provider, active: @ars.active, ip: @ars.ip}, :status => :ok
|
||||
else
|
||||
response.status = :unprocessable_entity
|
||||
respond_with @ars
|
||||
end
|
||||
allowed = [:password, :username, :active, :beta, :name, :provider, :id_int, :ip, :port, :continent, :country, :city, :subdivision, :latitude, :longitude]
|
||||
|
||||
update_hash = {}
|
||||
allowed.each do |attr|
|
||||
update_hash[attr] = params[attr] if params.has_key?(attr)
|
||||
end
|
||||
|
||||
if @ars.update_attributes(update_hash, as: :admin)
|
||||
render :json => @ars, :status => :ok
|
||||
else
|
||||
render :json => @ars.errors, :status => :unprocessable_entity
|
||||
end
|
||||
rescue => e
|
||||
render :json => {error: e.message, backtrace: e.backtrace.first(5)}, :status => 500
|
||||
end
|
||||
end
|
||||
|
||||
# create or update a client_artifact row
|
||||
def get_or_create
|
||||
begin
|
||||
name = params[:name]
|
||||
provider = params[:provider]
|
||||
active = params[:active]
|
||||
beta = params.has_key?(:beta) ? params[:beta] : true
|
||||
ip = params[:ip]
|
||||
username = params[:username]
|
||||
password = params[:password]
|
||||
topology = params[:topology]
|
||||
ars_id = params[:ars_id]
|
||||
|
||||
# Explicit field-based lookups
|
||||
ars = nil
|
||||
ars = JamRuby::Ars.find_by_id_int(ars_id) if ars_id
|
||||
ars ||= JamRuby::Ars.find_by_name(name) if name
|
||||
|
||||
if ars.nil?
|
||||
ars = JamRuby::Ars.new
|
||||
ars.name = name
|
||||
end
|
||||
|
||||
ars.id_int = ars_id if !ars_id.nil?
|
||||
ars.provider = provider
|
||||
ars.active = active
|
||||
ars.beta = params[:beta]
|
||||
ars.beta = beta
|
||||
ars.ip = ip
|
||||
ars.password = password
|
||||
ars.username = username
|
||||
if topology
|
||||
ars.city = topology['city']
|
||||
ars.country = topology['country']
|
||||
ars.continent = topology['continent']
|
||||
ars.latitude = topology['latitude']
|
||||
ars.longitude = topology['longitude']
|
||||
ars.subdivision = topology['subdivision']
|
||||
end
|
||||
ars.save!
|
||||
|
||||
@ars = ars
|
||||
render :json => {id_int: @ars.id_int, id: @ars.id, name: @ars.name, provider: @ars.provider, active: @ars.active, beta: @ars.beta, ip: @ars.ip}, :status => :ok
|
||||
rescue => e
|
||||
render :json => {error: e.message, backtrace: e.backtrace.first(5)}, :status => 500
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ JamAdmin::Application.routes.draw do
|
|||
match '/api/jam_tracks/released' => 'jam_track#dump_released', :via => :get, as: 'released_jamtracks_csv'
|
||||
|
||||
match '/api/arses/register' => 'arses#get_or_create', :via => :post
|
||||
match '/api/arses' => 'arses#index', :via => :get
|
||||
match '/api/arses/:id' => 'arses#update', :via => :post
|
||||
|
||||
mount Resque::Server.new, :at => "/resque"
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
# trigger build
|
||||
|
|
@ -12,3 +12,5 @@ REACT_APP_SITE_KEY=6Let8dgSAAAAAFheKGWrs6iaq_hIlPOZ2f3Bb56B
|
|||
REACT_APP_GOOGLE_ANALYTICS_ID=G-MC9BTWXWY4
|
||||
PUBLIC_URL=
|
||||
REACT_APP_COOKIE_DOMAIN=.jamkazam.local
|
||||
REACT_APP_RECURLY_PUBLIC_API_KEY=ewr1-hvDV1xQxDw0HPaaRFP4KNE
|
||||
REACT_APP_BRAINTREE_TOKEN=sandbox_pgjp8dvs_5v5rwm94m2vrfbms
|
||||
|
|
@ -8,4 +8,6 @@ REACT_APP_BITBUCKET_BUILD_NUMBER=dev
|
|||
REACT_APP_BITBUCKET_COMMIT=dev
|
||||
REACT_APP_ENV=development
|
||||
REACT_APP_COOKIE_DOMAIN=.jamkazam.com
|
||||
REACT_APP_GOOGLE_ANALYTICS_ID=G-MC9BTWXWY4
|
||||
REACT_APP_GOOGLE_ANALYTICS_ID=G-MC9BTWXWY4
|
||||
REACT_APP_RECURLY_PUBLIC_API_KEY=
|
||||
REACT_APP_BRAINTREE_TOKEN=
|
||||
|
|
@ -9,3 +9,5 @@ REACT_APP_RECAPTCHA_ENABLED=true
|
|||
REACT_APP_SITE_KEY=6Let8dgSAAAAAFheKGWrs6iaq_hIlPOZ2f3Bb56B
|
||||
REACT_APP_COOKIE_DOMAIN=.jamkazam.com
|
||||
REACT_APP_GOOGLE_ANALYTICS_ID=G-SPTNJRW7WB
|
||||
REACT_APP_RECURLY_PUBLIC_API_KEY=ewr1-hvDV1xQxDw0HPaaRFP4KNE
|
||||
REACT_APP_BRAINTREE_TOKEN=production_hc7z69yq_pwwc6zm3d478kfrh
|
||||
|
|
|
|||
|
|
@ -9,3 +9,5 @@ REACT_APP_RECAPTCHA_ENABLED=false
|
|||
REACT_APP_SITE_KEY=6Let8dgSAAAAAFheKGWrs6iaq_hIlPOZ2f3Bb56B
|
||||
REACT_APP_COOKIE_DOMAIN=.staging.jamkazam.com
|
||||
REACT_APP_GOOGLE_ANALYTICS_ID=G-8W0GTL53NT
|
||||
REACT_APP_RECURLY_PUBLIC_API_KEY=ewr1-AjUHUfcLtIsPdtetD4mj2x
|
||||
REACT_APP_BRAINTREE_TOKEN=sandbox_pgjp8dvs_5v5rwm94m2vrfbms
|
||||
|
|
|
|||
|
|
@ -38,3 +38,19 @@ npm run start
|
|||
|
||||
This will open it in a borwser window at http://beta.jamkazam.local:3000. Of course for it to work you also need Rails (web) app and websocket app (websocket-gateway) running.
|
||||
|
||||
## Working with JamTracks
|
||||
|
||||
if you have the latest from develop, you can go:
|
||||
```
|
||||
cd cicd
|
||||
npm install
|
||||
./export_personal_jamtracks.sh
|
||||
./generate.js
|
||||
open http://beta.jamkazam.local:4000/backing-tracks/ac-dc/back-in-black.html
|
||||
```
|
||||
|
||||
You can also do none of the above, and go straight to:
|
||||
http://beta.jamkazam.local:4000/public/backing-tracks/ac-dc/back-in-black
|
||||
|
||||
I tried to make it so the SPA has 'secret routes' to these pages, which is convenient for us dev & testing
|
||||
but also tried to make it convenient to run the cicd approach of actually generating separate pages for each landing page (which is what those 5 steps cover)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
/// <reference types="cypress" />
|
||||
import makeFakeUser from '../../factories/user';
|
||||
describe('Change Email Confirm Page', () => {
|
||||
beforeEach(() => {
|
||||
// Log in to the application or navigate to the account page
|
||||
// where the change email feature is available
|
||||
const currentUser = makeFakeUser({
|
||||
email: 'sam@example.com'
|
||||
});
|
||||
cy.stubAuthenticate({ ...currentUser });
|
||||
cy.intercept('POST', /\S+\/update_email/, { statusCode: 200, body: { ok: true } });
|
||||
});
|
||||
|
||||
it('should display the confirm page when visiting the confirm URL', () => {
|
||||
// Replace with a realistic token for your app if needed
|
||||
const token = 'dummy-confirm-token';
|
||||
|
||||
// Visit the confirm URL
|
||||
cy.visit(`/confirm-email-change?token=${token}`);
|
||||
|
||||
// Assert that the JKChangeEmailConfirm page is rendered
|
||||
// Adjust selectors/texts as per your actual component
|
||||
cy.contains('Change Email Confirmation').should('be.visible');
|
||||
cy.contains('Loading...').should('be.visible');
|
||||
// Optionally, check for the success message after the email update
|
||||
cy.wait(1000); // Wait for the email update to complete
|
||||
cy.contains('Your email has been successfully updated.').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
|
@ -1,23 +1,31 @@
|
|||
/// <reference types="cypress" />
|
||||
|
||||
import makeFakeUser from '../../factories/user';
|
||||
|
||||
describe('Unsubscribe from email link', () => {
|
||||
beforeEach(() =>{
|
||||
cy.intercept('POST', /\S+\/unsubscribe_user_match\/\S+/, { statusCode: 200, body: { ok: true } });
|
||||
beforeEach(() => {
|
||||
// cy.intercept('POST', /\S+\/unsubscribe_user_match\/\S+/, { statusCode: 200, body: { ok: true } });
|
||||
const currentUser = makeFakeUser({
|
||||
email: 'sam@example.com'
|
||||
});
|
||||
cy.stubAuthenticate({ ...currentUser });
|
||||
cy.intercept('POST', /\S+\/unsubscribe\/\S+/, { statusCode: 200, body: { ok: true } });
|
||||
})
|
||||
|
||||
it("redirect to home page if tok is not provided", () => {
|
||||
cy.visit('/unsubscribe');
|
||||
cy.location('pathname').should('eq', '/');
|
||||
cy.location('pathname').should('eq', '/errors/404');
|
||||
});
|
||||
|
||||
it.only("show unsubscribed message", () => {
|
||||
cy.visit('/unsubscribe?tok=123');
|
||||
cy.location('search')
|
||||
.should('equal', '?tok=123')
|
||||
.then((s) => new URLSearchParams(s))
|
||||
.invoke('get', 'tok')
|
||||
.should('equal', '123')
|
||||
cy.contains("successfully unsubscribed")
|
||||
it("show unsubscribed message", () => {
|
||||
cy.visit('/unsubscribe/123');
|
||||
// cy.location('search')
|
||||
// .should('equal', '?tok=123')
|
||||
// .then((s) => new URLSearchParams(s))
|
||||
// .invoke('get', 'tok')
|
||||
// .should('equal', '123')
|
||||
cy.contains("You have successfully unsubscribed from JamKazam emails.").should('be.visible');
|
||||
cy.contains("Loading...").should('not.exist');
|
||||
});
|
||||
|
||||
})
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -18,7 +18,7 @@ const JKRegistrationForm = ({ hasLabel, jamTrack, jamTrackArtistName }) => {
|
|||
const [password, setPassword] = useState('');
|
||||
const [isDisabled, setIsDisabled] = useState(true);
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
|
||||
|
||||
const { capchaToken, recaptchaRef, handleRecaptcha } = useRecaptcha();
|
||||
|
||||
const { addToCart } = useJamTrackShopping();
|
||||
|
|
@ -52,25 +52,31 @@ const JKRegistrationForm = ({ hasLabel, jamTrack, jamTrackArtistName }) => {
|
|||
toast.error(`Registration failed. Please try again.`);
|
||||
setIsDisabled(false);
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const addJamTrackToCart = async () => {
|
||||
try{
|
||||
try {
|
||||
await addToCart(jamTrack);
|
||||
}catch(error){
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('currentUser', currentUser);
|
||||
console.log('jamTrack', jamTrack);
|
||||
console.log('jamTrackArtistName', jamTrackArtistName);
|
||||
if (currentUser) {
|
||||
if(jamTrack){
|
||||
if (jamTrack) {
|
||||
console.log('adding jamtrack to cart');
|
||||
addJamTrackToCart();
|
||||
}else if(jamTrackArtistName){
|
||||
} else if (jamTrackArtistName) {
|
||||
console.log('redirecting to jamtracks artist landing');
|
||||
history.push(`/jamtracks?artist=${jamTrackArtistName}`);
|
||||
}else{
|
||||
history.push('/public/downloads');
|
||||
} else {
|
||||
console.log('redirecting to profile');
|
||||
history.push('/profile');
|
||||
}
|
||||
}
|
||||
}, [currentUser, jamTrack, jamTrackArtistName]);
|
||||
|
|
@ -130,9 +136,9 @@ const JKRegistrationForm = ({ hasLabel, jamTrack, jamTrackArtistName }) => {
|
|||
|
||||
{process.env.REACT_APP_RECAPTCHA_ENABLED === 'true' && (
|
||||
<FormGroup>
|
||||
<ReCAPTCHA
|
||||
sitekey={process.env.REACT_APP_SITE_KEY}
|
||||
ref={recaptchaRef}
|
||||
<ReCAPTCHA
|
||||
sitekey={process.env.REACT_APP_SITE_KEY}
|
||||
ref={recaptchaRef}
|
||||
onChange={handleRecaptcha}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
|
@ -140,7 +146,7 @@ const JKRegistrationForm = ({ hasLabel, jamTrack, jamTrackArtistName }) => {
|
|||
|
||||
<FormGroup>
|
||||
<Button color="primary" block className="mt-3" disabled={isDisabled}>
|
||||
{t('signup')}
|
||||
{t('signup')}
|
||||
</Button>
|
||||
</FormGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,9 @@ const Registration = () => {
|
|||
try {
|
||||
const response = await getJamTrackBySlug({ slug });
|
||||
const jamTrack = await response.json();
|
||||
console.log('jamTrack', jamTrack);
|
||||
setJamTrack(jamTrack);
|
||||
setJamTrackArtistName(jamTrack.original_artist);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
|
|
|||
|
|
@ -1,11 +1,27 @@
|
|||
import React, { Fragment } from 'react';
|
||||
import React, { Fragment, useEffect } from 'react';
|
||||
import { Col, Row } from 'reactstrap';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link, useHistory, useLocation } from 'react-router-dom';
|
||||
import LoginForm from '../LoginForm';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAuth } from '../../../context/UserAuth';
|
||||
|
||||
const Login = () => {
|
||||
const { t } = useTranslation('auth');
|
||||
const { isAuthenticated, isLoading } = useAuth();
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoading && isAuthenticated) {
|
||||
const { from } = location.state || { from: { pathname: '/profile' } };
|
||||
const finalDestination = from.pathname === '/' ? { pathname: '/profile' } : from;
|
||||
history.replace(finalDestination);
|
||||
}
|
||||
}, [isAuthenticated, isLoading, history, location]);
|
||||
|
||||
if (isLoading || isAuthenticated) {
|
||||
return null; // Or a loading spinner
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import JKEditProfile from '../page/JKEditProfile';
|
|||
import JKEditAccount from '../page/JKEditAccount';
|
||||
import JKAccountSubscription from '../page/JKAccountSubscription';
|
||||
import JKPaymentHistory from '../page/JKPaymentHistory';
|
||||
import JKPaymentMethod from '../page/JKPaymentMethod';
|
||||
import JKAccountPreferences from '../page/JKAccountPreferences';
|
||||
|
||||
import JKAffiliateProgram from '../affiliate/JKAffiliateProgram';
|
||||
|
|
@ -54,6 +55,9 @@ import JKMyJamTracks from '../jamtracks/JKMyJamTracks';
|
|||
import JKJamTrackShow from '../jamtracks/JKJamTrackShow';
|
||||
import JKPayPalConfirmation from '../shopping-cart/JKPayPalConfirmation';
|
||||
|
||||
import JKUnsubscribe from '../public/JKUnsubscribe';
|
||||
import JKConfirmEmailChange from '../public/JKConfirmEmailChange';
|
||||
|
||||
|
||||
//import loadable from '@loadable/component';
|
||||
//const DashboardRoutes = loadable(() => import('../../layouts/JKDashboardRoutes'));
|
||||
|
|
@ -129,7 +133,6 @@ function JKDashboardMain() {
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
const app = window.JK.JamKazam();
|
||||
|
||||
const jamServer = new window.JK.JamServer(app, function (event_type) {
|
||||
|
|
@ -141,6 +144,8 @@ function JKDashboardMain() {
|
|||
const clientInit = new window.JK.ClientInit();
|
||||
clientInit.init();
|
||||
|
||||
app.initialize();
|
||||
|
||||
window.JK.JamServer.connect() // singleton here defined in JamServer.js
|
||||
.done(function () {
|
||||
console.log('Jamserver connected...');
|
||||
|
|
@ -275,6 +280,10 @@ function JKDashboardMain() {
|
|||
}
|
||||
|
||||
useScript(`${process.env.REACT_APP_CLIENT_BASE_URL}/client_scripts`, initJKScripts);
|
||||
useScript('https://js.recurly.com/v4/recurly.js', () => {
|
||||
console.log('Recurly.js script loaded');
|
||||
window.recurly.configure(process.env.REACT_APP_RECURLY_PUBLIC_API_KEY);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={isFluid || isKanban ? 'container-fluid' : 'container'}>
|
||||
|
|
@ -296,6 +305,7 @@ function JKDashboardMain() {
|
|||
<PrivateRoute path="/account/identity" component={JKEditAccount} />
|
||||
<PrivateRoute path="/account/subscription" component={JKAccountSubscription} />
|
||||
<PrivateRoute path="/account/payments" component={JKPaymentHistory} />
|
||||
<PrivateRoute path="/account/payment-method" component={JKPaymentMethod} />
|
||||
<PrivateRoute path="/account/preferences" component={JKAccountPreferences} />
|
||||
<PrivateRoute path="/affiliate/program" component={JKAffiliateProgram} />
|
||||
<PrivateRoute path="/affiliate/payee" component={JKAffiliatePayee} />
|
||||
|
|
@ -312,6 +322,8 @@ function JKDashboardMain() {
|
|||
<PrivateRoute path="/checkout/success" component={JKCheckoutSuccess} />
|
||||
<PrivateRoute path="/checkout" component={JKCheckout} />
|
||||
<PrivateRoute path="/applaunch" component={JKAppLaunch} />
|
||||
<PrivateRoute path="/unsubscribe/:tok" exact component={JKUnsubscribe} />
|
||||
<PrivateRoute path="/confirm-email-change" exact component={JKConfirmEmailChange} />
|
||||
{/*Redirect*/}
|
||||
<Redirect to="/errors/404" />
|
||||
</Switch>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ const awesome9 = '/img/landing/jamtracks/Top 10 Image - Number 9.webp';
|
|||
const BodyComponent = ({
|
||||
id = "1",
|
||||
plan_code = "jamtrack-acdc-backinblack",
|
||||
slug = "ac-dc-back-in-black",
|
||||
slug = "acdc-back-in-black",
|
||||
artist = "AC/DC (defaulted)",
|
||||
song = "Back in Black (defaulted)",
|
||||
provided_jam_track = null
|
||||
|
|
|
|||
|
|
@ -23,12 +23,24 @@ const ProfileDropdown = () => {
|
|||
|
||||
const handleLogout = async event => {
|
||||
event.preventDefault();
|
||||
const cookieDomain = `.${process.env.REACT_APP_ORIGIN}`;
|
||||
console.log('handleLogout: cookie: ', cookieDomain);
|
||||
removeCookie('remember_token', {
|
||||
domain: `.${process.env.REACT_APP_ORIGIN}`
|
||||
domain: cookieDomain,
|
||||
path: '/'
|
||||
});
|
||||
setCurrentUser(null);
|
||||
// This will cause the server to remove the cookie TOO (overkill),
|
||||
// but this code has a side effect because something, somewhere,
|
||||
// client-side history pushes to /auth/login because of this function.
|
||||
// ...
|
||||
await logout();
|
||||
// But I don't want to count on that side effect (defensive against future changes), so I force a page location hit here
|
||||
window.location.href = "/auth/login";
|
||||
// And here's the final bit; force a full page refresh ANYWAY.
|
||||
// This is the only way we seem to really clear
|
||||
// 'login state' cleanly at this point
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -0,0 +1,480 @@
|
|||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import {
|
||||
Card,
|
||||
CardBody,
|
||||
Col,
|
||||
Button,
|
||||
Row,
|
||||
CustomInput,
|
||||
Label
|
||||
} from 'reactstrap';
|
||||
import Flex from '../common/Flex';
|
||||
import FalconCardHeader from '../common/FalconCardHeader';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import iconPaymentMethodsGrid from '../../assets/img/icons/icon-payment-methods-grid.png';
|
||||
import iconPaypalFull from '../../assets/img/icons/icon-paypal-full.png';
|
||||
import { toast } from 'react-toastify';
|
||||
import { updatePayment } from '../../helpers/rest';
|
||||
import { useAuth } from '../../context/UserAuth';
|
||||
import { getBillingInfo, getUserDetail, getCountries } from '../../helpers/rest';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import Select from 'react-select';
|
||||
import { useResponsive } from '@farfetch/react-context-responsive';
|
||||
|
||||
const JKPaymentMethod = () => {
|
||||
const { t } = useTranslation('account');
|
||||
const [billingInfo, setBillingInfo] = useState({});
|
||||
const [hasStoredCreditCard, setHasStoredCreditCard] = useState(false);
|
||||
const [paymentMethod, setPaymentMethod] = useState('credit-card');
|
||||
const { currentUser } = useAuth();
|
||||
const [countries, setCountries] = useState([]);
|
||||
const labelClassName = 'ls text-600 font-weight-semi-bold mb-0';
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [isCardValid, setIsCardValid] = useState(false);
|
||||
const { greaterThan } = useResponsive();
|
||||
|
||||
const elementsRef = useRef(null);
|
||||
const formRef = useRef(null);
|
||||
const recurlyConfigured = useRef(false);
|
||||
const paypal = useRef(null);
|
||||
|
||||
const {
|
||||
register,
|
||||
control,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
formState: { errors }
|
||||
} = useForm({
|
||||
defaultValues: {
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
address1: '',
|
||||
address2: '',
|
||||
city: '',
|
||||
state: '',
|
||||
zip: '',
|
||||
country: 'US',
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (currentUser) {
|
||||
populateUserData();
|
||||
}
|
||||
}, [currentUser]);
|
||||
|
||||
const populateUserData = async () => {
|
||||
const options = {
|
||||
id: currentUser.id
|
||||
};
|
||||
try {
|
||||
const userResp = await getUserDetail(options);
|
||||
const userData = await userResp.json();
|
||||
if (userData.has_recurly_account) {
|
||||
setHasStoredCreditCard(userData['has_stored_credit_card?']);
|
||||
await populateBillingAddress();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to get user details:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const populateBillingAddress = async () => {
|
||||
try {
|
||||
const resp = await getBillingInfo();
|
||||
const data = await resp.json();
|
||||
const bi = data.billing_info;
|
||||
setBillingInfo(bi);
|
||||
} catch (error) {
|
||||
console.error('Failed to get billing info:', error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (currentUser) {
|
||||
fetchCountries();
|
||||
}
|
||||
}, [currentUser]);
|
||||
|
||||
const fetchCountries = () => {
|
||||
getCountries()
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
})
|
||||
.then(data => {
|
||||
setCountries(data.countriesx);
|
||||
})
|
||||
.catch(error => console.log(error));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (billingInfo) {
|
||||
setValue('first_name', billingInfo.first_name || '');
|
||||
setValue('last_name', billingInfo.last_name || '');
|
||||
setValue('address1', billingInfo.address1 || '');
|
||||
setValue('address2', billingInfo.address2 || '');
|
||||
setValue('city', billingInfo.city || '');
|
||||
setValue('state', billingInfo.state || '');
|
||||
setValue('zip', billingInfo.zip || '');
|
||||
setValue('country', billingInfo.country || 'US');
|
||||
}
|
||||
}, [billingInfo, setValue]);
|
||||
|
||||
const handleCountryChange = selectedOption => {
|
||||
setValue('country', selectedOption.value);
|
||||
};
|
||||
|
||||
const recurlyContainerRef = useRef();
|
||||
|
||||
useEffect(() => {
|
||||
if (!recurlyContainerRef.current || !window.recurly || recurlyConfigured.current) return;
|
||||
|
||||
window.recurly.configure({ publicKey: process.env.REACT_APP_RECURLY_PUBLIC_API_KEY });
|
||||
const elements = window.recurly.Elements();
|
||||
const cardElement = elements.CardElement();
|
||||
|
||||
cardElement.attach(recurlyContainerRef.current);
|
||||
|
||||
cardElement.on('change', (event) => {
|
||||
setIsCardValid(event.complete && !event.error);
|
||||
});
|
||||
|
||||
//then load paypal:
|
||||
const paypalInst = window.recurly.PayPal({ braintree: { clientAuthorization: process.env.REACT_APP_BRAINTREE_TOKEN } })
|
||||
paypal.current = paypalInst;
|
||||
paypal.current.on('error', onPayPalError);
|
||||
paypal.current.on('token', onPayPalToken);
|
||||
|
||||
elementsRef.current = elements;
|
||||
recurlyConfigured.current = true;
|
||||
|
||||
return () => {
|
||||
// Optional cleanup if the component unmounts
|
||||
recurlyContainerRef.current.innerHTML = '';
|
||||
recurlyConfigured.current = false;
|
||||
};
|
||||
}, [recurlyContainerRef.current]);
|
||||
|
||||
const onPayPalError = (error) => {
|
||||
console.error('PayPal Error:', error);
|
||||
toast.error('PayPal Error: ' + (error.message || t('payment_method.alerts.try_again')));
|
||||
setSubmitting(false);
|
||||
}
|
||||
|
||||
const onPayPalToken = (token) => {
|
||||
handleUpdatePayment(token);
|
||||
}
|
||||
|
||||
const handleUpdatePayment = (token) => {
|
||||
updatePayment({ recurly_token: token.id }).then((response) => {
|
||||
setHasStoredCreditCard(true);
|
||||
toast.success(t('payment_method.alerts.payment_method_updated'));
|
||||
}).catch((error) => {
|
||||
console.error('Error updating payment with PayPal token:', error);
|
||||
if (error.response && error.response.data && error.response.data.message) {
|
||||
toast.error(error.response.data.message);
|
||||
} else {
|
||||
console.error('Error updating payment with PayPal token:', error);
|
||||
toast.error(t('payment_method.alerts.card_update_error'));
|
||||
}
|
||||
}).finally(() => {
|
||||
setSubmitting(false);
|
||||
});
|
||||
};
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
setSubmitting(true);
|
||||
if (paymentMethod === 'paypal') { // PayPal payment method
|
||||
handoverToPaypal();
|
||||
return;
|
||||
} else { // Credit Card payment method
|
||||
|
||||
if (!elementsRef.current) {
|
||||
console.error('Recurly elementsRef.current is not ready');
|
||||
setSubmitting(false);
|
||||
return;
|
||||
}
|
||||
if (!formRef.current) {
|
||||
console.error('formRef.current is not ready');
|
||||
setSubmitting(false);
|
||||
return;
|
||||
}
|
||||
|
||||
window.recurly.token(elementsRef.current, formRef.current, (err, token) => {
|
||||
if (err) {
|
||||
console.error('Recurly token error:', err);
|
||||
toast.error(t('payment_method.alerts.card_processing_error'));
|
||||
setSubmitting(false);
|
||||
} else {
|
||||
console.log('Recurly token:', token.id);
|
||||
// send token.id to backend
|
||||
handleUpdatePayment(token);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const handoverToPaypal = () => {
|
||||
// Handover to Paypal
|
||||
setSubmitting(true);
|
||||
paypal.current.start()
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<FalconCardHeader title={t('payment_method.page_title')} titleClass="font-weight-bold" />
|
||||
<CardBody className="pt-3" style={{ backgroundColor: '#edf2f9' }}>
|
||||
<div className='mb-3'>
|
||||
{hasStoredCreditCard ? (
|
||||
<span>
|
||||
<strong>{t('payment_method.help_text_has_card')}</strong>
|
||||
</span>
|
||||
) : (
|
||||
<span>
|
||||
<strong>{t('payment_method.help_text_no_card')}</strong>
|
||||
</span>
|
||||
)}
|
||||
{t('payment_method.help_text')}
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)} ref={formRef}>
|
||||
<Card style={{ width: greaterThan.sm ? "90%" : '100%' }} className='mx-auto'>
|
||||
<FalconCardHeader title={t('payment_method.header')} titleTag="h5" />
|
||||
<CardBody>
|
||||
<Row>
|
||||
<Col className="mb-2" xs={12} md={6}>
|
||||
<Row className="mb-2">
|
||||
<Col xs={12} md={5} lg={4} className="text-md-right">
|
||||
<Label for="first_name" className={labelClassName}>
|
||||
{t('payment_method.first_name')}
|
||||
</Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<input {...register('first_name', { required: t('payment_method.validations.first_name.required') })} className="form-control" data-recurly="first_name" />
|
||||
{errors.first_name && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.first_name.message}</small>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mb-2">
|
||||
<Col xs={12} md={5} lg={4} className="text-md-right">
|
||||
<Label for="last_name" className={labelClassName}>
|
||||
{t('payment_method.last_name')}
|
||||
</Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<input {...register('last_name', { required: t('payment_method.validations.last_name.required') })} className="form-control" data-recurly="last_name" />
|
||||
{errors.last_name && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.last_name.message}</small>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mb-2">
|
||||
<Col xs={12} md={5} lg={4} className="text-md-right">
|
||||
<Label for="address1" className={labelClassName}>
|
||||
{t('payment_method.address1')}
|
||||
</Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<input {...register('address1', { required: t('payment_method.validations.address1.required') })} className="form-control" data-recurly="address1" />
|
||||
{errors.address1 && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.address1.message}</small>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mb-2">
|
||||
<Col xs={12} md={5} lg={4} className="text-md-right">
|
||||
<Label for="address2" className={labelClassName}>
|
||||
{t('payment_method.address2')}
|
||||
</Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<input {...register('address2')} className="form-control" data-recurly="address2" />
|
||||
{errors.address2 && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.address2.message}</small>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mb-2">
|
||||
<Col xs={12} md={5} lg={4} className="text-md-right">
|
||||
<Label for="city" className={labelClassName}>
|
||||
{t('payment_method.city')}
|
||||
</Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<input {...register('city', { required: t('payment_method.validations.city.required') })} className="form-control" data-recurly="city" />
|
||||
{errors.city && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.city.message}</small>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mb-2">
|
||||
<Col xs={12} md={5} lg={4} className="text-md-right">
|
||||
<Label for="state" className={labelClassName}>
|
||||
{t('payment_method.state')}
|
||||
</Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<input {...register('state', { required: t('payment_method.validations.state.required') })} className="form-control" data-recurly="state" />
|
||||
{errors.state && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.state.message}</small>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mb-2">
|
||||
<Col xs={12} md={5} lg={4} className="text-md-right">
|
||||
<Label for="zip" className={labelClassName}>
|
||||
{t('payment_method.zip_code')}
|
||||
</Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<input
|
||||
{...register('zip', { required: t('payment_method.validations.zip_code.required') })}
|
||||
className="form-control" data-recurly="postal_code"
|
||||
/>
|
||||
{errors.zip && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.zip.message}</small>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mb-2">
|
||||
<Col xs={12} md={5} lg={4} className="text-md-right">
|
||||
<Label for="country" className={labelClassName}>
|
||||
{t('payment_method.country')}
|
||||
</Label>
|
||||
</Col>
|
||||
<Col>
|
||||
<Controller
|
||||
name="country"
|
||||
control={control}
|
||||
rules={{ required: t('payment_method.validations.country.required') }}
|
||||
render={({ field: { onChange, value } }) => {
|
||||
const country = countries.find(country => country.countrycode === value);
|
||||
if (!country) {
|
||||
return (
|
||||
<Select
|
||||
data-testid="countrySelect"
|
||||
data-recurly="country"
|
||||
onChange={handleCountryChange}
|
||||
options={countries.map(c => {
|
||||
return { value: c.countrycode, label: c.countryname };
|
||||
})}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Select
|
||||
data-testid="countrySelect"
|
||||
data-recurly="country"
|
||||
value={{ value: country.countrycode, label: country.countryname }}
|
||||
onChange={handleCountryChange}
|
||||
options={countries.map(c => {
|
||||
return { value: c.countrycode, label: c.countryname };
|
||||
})}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<input type="hidden" name="country" data-recurly="country" {...register('country')} />
|
||||
{errors.country && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.country.message}</small>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col xs={12} md={6} className="mb-2 pl-5">
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
<CustomInput
|
||||
label={
|
||||
<Flex align="center" className="mb-2 fs-1">
|
||||
<span>{t('payment_method.credit_card')}</span>
|
||||
</Flex>
|
||||
}
|
||||
id="credit-card"
|
||||
value="credit-card"
|
||||
checked={paymentMethod === 'credit-card'}
|
||||
onChange={({ target }) => setPaymentMethod(target.value)}
|
||||
type="radio"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col sm={8}>
|
||||
<div id="recurly-elements" ref={recurlyContainerRef}></div>
|
||||
{!isCardValid && errors.recurly && (
|
||||
<div className="text-danger">
|
||||
<small>{errors.recurly.message}</small>
|
||||
</div>
|
||||
)}
|
||||
<input type="hidden" name="recurly-token" data-recurly="token" />
|
||||
</Col>
|
||||
<div className="col-4 text-center pt-2 d-none d-sm-block">
|
||||
<div className="rounded p-2 mt-3 bg-100">
|
||||
<div className="text-uppercase fs--2 font-weight-bold">{t('payment_method.we_accept')}</div>
|
||||
<img src={iconPaymentMethodsGrid} alt="" width="120" />
|
||||
</div>
|
||||
</div>
|
||||
</Row>
|
||||
<hr />
|
||||
<Row className="mt-3">
|
||||
<Col xs={12}>
|
||||
<CustomInput
|
||||
label={<img className="pull-right" src={iconPaypalFull} height="20" alt="" />}
|
||||
id="paypal"
|
||||
value="paypal"
|
||||
checked={paymentMethod === 'paypal'}
|
||||
onChange={({ target }) => setPaymentMethod(target.value)}
|
||||
type="radio"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<div className="d-flex justify-content-center">
|
||||
<Button
|
||||
color="primary"
|
||||
type="submit"
|
||||
disabled={submitting}
|
||||
className="mt-3"
|
||||
>
|
||||
{submitting ? t('payment_method.submitting') : t('payment_method.save_payment_info')}
|
||||
</Button>
|
||||
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="fs--1 mt-3 mb-0">
|
||||
{t('payment_method.aggreement.text1')} <strong>{t('payment_method.aggreement.text2')} </strong>{t('payment_method.aggreement.text3')}{' '}
|
||||
<br />
|
||||
<a href="https://www.jamkazam.com/corp/terms" target='_blank'>{t('payment_method.aggreement.terms_of_service')}</a>
|
||||
</p>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</form>
|
||||
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default JKPaymentMethod;
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
import React from 'react'
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { Card, CardBody, CardText, CardTitle } from 'reactstrap';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { updateEmail } from '../../helpers/rest';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const JKConfirmEmailChange = () => {
|
||||
const { t } = useTranslation("account");
|
||||
const location = useLocation();
|
||||
const params = new URLSearchParams(location.search);
|
||||
const token = params.get('token');
|
||||
|
||||
const [success, setSuccess] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (token) {
|
||||
updateEmail(token)
|
||||
.then(response => {
|
||||
if (response.status === 200) {
|
||||
setSuccess(true);
|
||||
} else {
|
||||
setSuccess(false);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
setSuccess(false);
|
||||
setError(t('identity.changed_email_confirmation.error'));
|
||||
}).finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
}, [token]);
|
||||
|
||||
return (
|
||||
<Card style={{ width: '25rem', margin: '2rem auto' }}>
|
||||
<CardBody>
|
||||
<CardTitle className="mb-2">
|
||||
{t('identity.changed_email_confirmation.title')}
|
||||
</CardTitle>
|
||||
<>
|
||||
{loading && <div className="text-muted"><span className="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>{t('identity.changed_email_confirmation.loading')}</div>}
|
||||
{success && <div className="text-success">{t('identity.changed_email_confirmation.success')}</div>}
|
||||
{error && <div className="text-danger">{error}</div>}
|
||||
</>
|
||||
</CardBody>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default JKConfirmEmailChange
|
||||
|
|
@ -1,16 +1,18 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { Card, CardBody, CardText, CardTitle } from 'reactstrap';
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Card, CardBody, CardText, CardTitle } from 'reactstrap';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useBrowserQuery } from '../../context/BrowserQuery';
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { useHistory, useParams } from "react-router-dom";
|
||||
|
||||
|
||||
const unsubscribeFromNewUsersWeeklyEmail = (token) => {
|
||||
|
||||
const baseUrl = process.env.REACT_APP_LEGACY_BASE_URL
|
||||
|
||||
|
||||
|
||||
const baseUrl = process.env.REACT_APP_CLIENT_BASE_URL
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch(`${baseUrl}/unsubscribe_user_match/${token}`,
|
||||
fetch(`${baseUrl}/unsubscribe_user_match/${token}`,
|
||||
{ method: 'POST' }
|
||||
).then(response => {
|
||||
if (response.ok) {
|
||||
|
|
@ -22,43 +24,64 @@ const unsubscribeFromNewUsersWeeklyEmail = (token) => {
|
|||
})
|
||||
}
|
||||
|
||||
const unsubscribe = (token) => {
|
||||
const baseUrl = process.env.REACT_APP_CLIENT_BASE_URL
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch(`${baseUrl}/unsubscribe/${token}`, { method: 'POST', headers: { 'Content-Type': 'application/json', accept: 'application/json' } })
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
resolve(response)
|
||||
} else {
|
||||
reject(response)
|
||||
}
|
||||
}).catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function JKUnsubscribe() {
|
||||
const {t} = useTranslation()
|
||||
const queryObj = useBrowserQuery();
|
||||
const { t } = useTranslation("unsubscribe");
|
||||
const history = useHistory()
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [success, setSuccess] = useState(false)
|
||||
const [error, setError] = useState(null)
|
||||
const { tok } = useParams();
|
||||
|
||||
useEffect(() => {
|
||||
const token = queryObj.get('tok')
|
||||
if(token){
|
||||
unsubscribeFromNewUsersWeeklyEmail(token)
|
||||
.then((resp) => {
|
||||
if(resp.ok){
|
||||
setSuccess(true)
|
||||
}
|
||||
})
|
||||
.catch(error => console.error(error))
|
||||
}else{
|
||||
if (tok) {
|
||||
unsubscribe(tok)
|
||||
.then((resp) => {
|
||||
if (resp.ok) {
|
||||
setSuccess(true)
|
||||
} else {
|
||||
setSuccess(false)
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
setError(error)
|
||||
}).finally(() => {
|
||||
setLoading(false)
|
||||
});
|
||||
} else {
|
||||
history.push('/')
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
return (
|
||||
|
||||
|
||||
<Card color={ success ? 'success' : 'light' } style={{ width: '25rem', margin: '2rem auto' }}>
|
||||
|
||||
<Card style={{ width: '25rem', margin: '2rem auto' }}>
|
||||
<CardBody>
|
||||
<CardTitle className="mb-2">Unsubscribe From Weekly Email</CardTitle>
|
||||
<CardText>
|
||||
{
|
||||
success?
|
||||
'You have successfully unsubscribed from weekly emails on newly joined musicians having low internet latency to you.' :
|
||||
'Unsubscribing...'
|
||||
}
|
||||
</CardText>
|
||||
<CardTitle className="mb-2">{t('page_title')}</CardTitle>
|
||||
<>
|
||||
{loading && <div className="text-muted"><span className="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>{t('loading')}</div>}
|
||||
{success && <div className="text-success">{t('success')}</div>}
|
||||
{error && <div className="text-danger">{t('error')}</div>}
|
||||
</>
|
||||
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,99 @@
|
|||
/**
|
||||
* meta_tracking.js
|
||||
* A standalone module to capture and persist Meta attribution signals (fbclid, _fbp) in cookies.
|
||||
*
|
||||
* Logic adapted from web/app/assets/javascripts/meta_tracking.js for React environment.
|
||||
* - Checks URL for `fbclid` and sets `_fbc` cookie.
|
||||
* - Checks for `_fbp` cookie; if missing, generates and sets it.
|
||||
*/
|
||||
|
||||
const MetaTracking = {
|
||||
init: function () {
|
||||
const location = window.location;
|
||||
this.handleFbc(location.search);
|
||||
this.handleFbp();
|
||||
this.handleUtm(location.search);
|
||||
},
|
||||
|
||||
// 1. Parsing and storing _fbc (Click ID)
|
||||
handleFbc: function (searchParams) {
|
||||
const fbclid = this.getQueryParam('fbclid', searchParams);
|
||||
|
||||
if (fbclid) {
|
||||
const version = 'fb';
|
||||
const subdomainIndex = 1; // 1 = example.com
|
||||
const creationTime = new Date().getTime(); // Unix timestamp in ms
|
||||
|
||||
// Format: fb.1.timestamp.id
|
||||
const fbcValue = `${version}.${subdomainIndex}.${creationTime}.${fbclid}`;
|
||||
|
||||
this.setCookie('_fbc', fbcValue, 90);
|
||||
}
|
||||
},
|
||||
|
||||
handleUtm: function (searchParams) {
|
||||
if (!searchParams) return;
|
||||
|
||||
const query = searchParams.substring(1);
|
||||
const vars = query.split('&');
|
||||
vars.forEach(v => {
|
||||
const pair = v.split('=');
|
||||
if (pair.length === 2) {
|
||||
const key = decodeURIComponent(pair[0]);
|
||||
const value = decodeURIComponent(pair[1]);
|
||||
if (key.indexOf('utm_') === 0) {
|
||||
this.setCookie(key, value, 90);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 2. Handling _fbp (Browser ID)
|
||||
handleFbp: function () {
|
||||
if (!this.getCookie('_fbp')) {
|
||||
const version = 'fb';
|
||||
const subdomainIndex = 1;
|
||||
const creationTime = new Date().getTime();
|
||||
const randomInt = Math.floor(Math.random() * 10000000000); // 10-digit random number
|
||||
|
||||
// Format: fb.1.timestamp.randomDigits
|
||||
const fbpValue = `${version}.${subdomainIndex}.${creationTime}.${randomInt}`;
|
||||
|
||||
this.setCookie('_fbp', fbpValue, 90);
|
||||
}
|
||||
},
|
||||
|
||||
// Helper: Get query param by name
|
||||
getQueryParam: function (name, search) {
|
||||
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
|
||||
const regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
|
||||
const results = regex.exec(search);
|
||||
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
|
||||
},
|
||||
|
||||
// Helper: Set cookie
|
||||
setCookie: function (name, value, days) {
|
||||
let expires = "";
|
||||
if (days) {
|
||||
const date = new Date();
|
||||
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||
expires = "; expires=" + date.toUTCString();
|
||||
}
|
||||
// Ensure path is root and domain is included if needed (defaults to current host)
|
||||
document.cookie = name + "=" + (value || "") + expires + "; path=/";
|
||||
},
|
||||
|
||||
// Helper: Get cookie
|
||||
getCookie: function (name) {
|
||||
const nameEQ = name + "=";
|
||||
const ca = document.cookie.split(';');
|
||||
for (let i = 0; i < ca.length; i++) {
|
||||
let c = ca[i];
|
||||
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
|
||||
if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export default MetaTracking;
|
||||
|
|
@ -101,8 +101,8 @@ export const getInstruments = () => {
|
|||
export const getCurrentUser = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
apiFetch('/me')
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error))
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -289,7 +289,7 @@ export const getCities = (countryId, regionId) => {
|
|||
export const postUpdateAccountEmail = (userId, options) => {
|
||||
const { email, current_password } = options;
|
||||
return new Promise((resolve, reject) => {
|
||||
apiFetch(`/users/${userId}/update_email`, {
|
||||
apiFetch(`/users/${userId}/update_email_alt`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ update_email: email, current_password })
|
||||
})
|
||||
|
|
@ -339,7 +339,7 @@ export const requstResetForgotPassword = email => {
|
|||
})
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const resetForgotPassword = (options = {}) => {
|
||||
|
|
@ -491,18 +491,16 @@ export const getJamTrackPublic = options => {
|
|||
const { plan_code } = options;
|
||||
return new Promise((resolve, reject) => {
|
||||
// This does not make sense; historical reasons here
|
||||
apiFetch(`/jamtracks/band/${plan_code}?${new URLSearchParams({plan_code})}`)
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error));
|
||||
apiFetch(`/jamtracks/band/${plan_code}?${new URLSearchParams({ plan_code })}`)
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
};
|
||||
|
||||
export const getJamTrackBySlug = options => {
|
||||
const { slug } = options;
|
||||
return new Promise((resolve, reject) => {
|
||||
apiFetch(`/jamtracks/${slug}`, {
|
||||
baseUrl: process.env.REACT_APP_CLIENT_BASE_URL
|
||||
})
|
||||
apiFetch(`/jamtracks/public/${slug}`)
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
|
|
@ -554,6 +552,18 @@ export const getBillingInfo = () => {
|
|||
});
|
||||
};
|
||||
|
||||
export const updateBillingInfo = (options = {}) => {
|
||||
const params = { billing_info: options };
|
||||
return new Promise((resolve, reject) => {
|
||||
apiFetch(`/recurly/update_billing_info`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
};
|
||||
|
||||
export const createRecurlyAccount = (options = {}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
apiFetch(`/recurly/create_account`, {
|
||||
|
|
@ -671,7 +681,7 @@ export const createAlert = (subject, data) => {
|
|||
return new Promise((resolve, reject) => {
|
||||
apiFetch(`/alerts`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({subject, data})
|
||||
body: JSON.stringify({ subject, data })
|
||||
})
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error));
|
||||
|
|
@ -688,8 +698,8 @@ export const getClientDownloads = () => {
|
|||
export const getObsPluginDownloads = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
apiFetch(`/artifacts/OBSPlugin`)
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error));
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -704,3 +714,61 @@ export const paypalPlaceOrder = (options = {}) => {
|
|||
.catch(error => reject(error));
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
export const submitStripe = (options = {}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
apiFetch(`/stripe`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options)
|
||||
})
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error));
|
||||
})
|
||||
}
|
||||
|
||||
// function updatePayment(options) {
|
||||
// options = options || {}
|
||||
// return $.ajax({
|
||||
// type: "POST",
|
||||
// url: '/api/recurly/update_payment',
|
||||
// dataType: "json",
|
||||
// contentType: 'application/json',
|
||||
// data: JSON.stringify(options)
|
||||
// })
|
||||
// }
|
||||
|
||||
export const updatePayment = (options = {}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
apiFetch(`/recurly/update_payment`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options)
|
||||
})
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
};
|
||||
|
||||
// function postUpdateEmail(email, current_password) {
|
||||
|
||||
// var url = "/api/users/" + context.JK.currentUserId + "/update_email";
|
||||
// return $.ajax({
|
||||
// type: "POST",
|
||||
// dataType: "json",
|
||||
// contentType: 'application/json',
|
||||
// url: url,
|
||||
// data: JSON.stringify({ update_email: email, current_password: current_password }),
|
||||
// processData: false
|
||||
// });
|
||||
// }
|
||||
|
||||
export const updateEmail = (userId, email, current_password) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
apiFetch(`/users/${userId}/update_email`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ update_email: email, current_password })
|
||||
})
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
|
@ -26,6 +26,12 @@
|
|||
"confirmation_email_sent": "A confirmation email has been sent to your email address. Please click the link in the email to confirm your email address."
|
||||
}
|
||||
},
|
||||
"changed_email_confirmation": {
|
||||
"title": "Change Email Confirmation",
|
||||
"loadding": "Loading...",
|
||||
"success": "Your email has been successfully changed.",
|
||||
"error": "An error occurred while confirming your email change. Please try again later."
|
||||
},
|
||||
"password_form": {
|
||||
"title": "Password",
|
||||
"help_text": "To update the password associated with your account, enter your current password (for security reasons) and the new password, and click the \"Save Password\" button.",
|
||||
|
|
@ -109,5 +115,68 @@
|
|||
"no_payments": "No payments found.",
|
||||
"load_more": "Load More",
|
||||
"loading": "Loading..."
|
||||
},
|
||||
"payment_method": {
|
||||
"page_title": "Payment Method",
|
||||
"header": "Address and Payment Method",
|
||||
"help_text_no_card": "You do not currently have a payment method on file.",
|
||||
"help_text_has_card": "You currently have a payment method on file.",
|
||||
"help_text": "To update your payment method, first enter your billing address and click the save button. If credit card, enter your card information and click the Save button. If PayPal, click the save button and follow PayPal's on-screen instructions to sign into your account and authorize payment to JamKazam.",
|
||||
"credit_card_number": "Credit Card Number",
|
||||
"expiration_date": "Expiration Date (MM/YY)",
|
||||
"cvv": "CVV",
|
||||
"submit": "Save Payment Method",
|
||||
"first_name": "First Name",
|
||||
"last_name": "Last Name",
|
||||
"address1": "Address 1",
|
||||
"address2": "Address 2",
|
||||
"city": "City",
|
||||
"state": "State or Region",
|
||||
"zip_code": "Zip/Postal Code",
|
||||
"country": "Country",
|
||||
"credit_card": "Credit Card",
|
||||
"paypal": "PayPal",
|
||||
"we_accept": "We accept",
|
||||
"submitting": "Submitting...",
|
||||
"save_payment_info": "Save Payment Information",
|
||||
"validations": {
|
||||
"first_name": {
|
||||
"required": "First Name is required"
|
||||
},
|
||||
"last_name": {
|
||||
"required": "Last Name is required"
|
||||
},
|
||||
"address1": {
|
||||
"required": "Address Line 1 is required"
|
||||
},
|
||||
"city": {
|
||||
"required": "City is required"
|
||||
},
|
||||
"state": {
|
||||
"required": "State or Region is required"
|
||||
},
|
||||
"zip_code": {
|
||||
"required": "Zip/Postal Code is required"
|
||||
},
|
||||
"country": {
|
||||
"required": "Country is required"
|
||||
},
|
||||
"card": {
|
||||
"invalid": "Credit card details are invalid"
|
||||
}
|
||||
},
|
||||
"aggreement": {
|
||||
"text1": "By clicking",
|
||||
"text2": "Save Payment Information",
|
||||
"text3": "you agree to JamKazam's",
|
||||
"terms_of_service": "Terms of Service"
|
||||
},
|
||||
"alerts": {
|
||||
"try_again": "Please try again.",
|
||||
"payment_method_updated": "Your payment method has been successfully updated.",
|
||||
"card_update_error": "Failed to update payment method. Please try again later.",
|
||||
"card_processing_error": "There was an error processing your card. Please check your details and try again.",
|
||||
"billing_update_error": "There was an error processing your billing information. Please check your details and try again."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
{
|
||||
"page_title": "Unsubscribe"
|
||||
"page_title": "Unsubscribe from JamKazam emails",
|
||||
"success": "You have successfully unsubscribed from JamKazam emails.",
|
||||
"error": "An error occurred while unsubscribing. Please try again later.",
|
||||
"loading": "Loading..."
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import React, {useEffect} from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
import { toast, ToastContainer } from 'react-toastify';
|
||||
import { CloseButton, Fade } from '../components/common/Toast';
|
||||
|
|
@ -8,6 +8,7 @@ import ErrorLayout from './ErrorLayout';
|
|||
import BuildMeta from "./JKBuildMeta";
|
||||
|
||||
import loadable from '@loadable/component';
|
||||
import MetaTracking from "../helpers/MetaTracking";
|
||||
const AuthBasicLayout = loadable(() => import('./JKAuthBasicLayout'));
|
||||
const PublicLayout = loadable(() => import('./JKPublicLayout'));
|
||||
|
||||
|
|
@ -15,6 +16,7 @@ const Layout = () => {
|
|||
useEffect(() => {
|
||||
AuthBasicLayout.preload();
|
||||
PublicLayout.preload();
|
||||
MetaTracking.init();
|
||||
|
||||
//see if there is affiliate in query string and save it as cookie
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
|
@ -28,7 +30,7 @@ const Layout = () => {
|
|||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<>
|
||||
<Switch>
|
||||
<Route path="/errors" component={ErrorLayout} />
|
||||
<Route path="/auth" component={AuthBasicLayout} />
|
||||
|
|
@ -37,7 +39,7 @@ const Layout = () => {
|
|||
</Switch>
|
||||
<ToastContainer transition={Fade} closeButton={<CloseButton />} position={toast.POSITION.BOTTOM_RIGHT} />
|
||||
<BuildMeta />
|
||||
</>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import JKUnsubscribe from '../components/public/JKUnsubscribe';
|
|||
import JKDownloads from '../components/public/JKDownloads';
|
||||
import JKDownloadsLegacy from '../components/public/JKDownloadsLegacy';
|
||||
import JKObsDownloads from '../components/public/JKObsDownloads';
|
||||
import JKConfirmEmailChange from '../components/public/JKConfirmEmailChange';
|
||||
import JKJamTracksLanding from '../components/jamtracks/JKJamTracksLandingDev';
|
||||
import JKJamTracksArtistLanding from '../components/jamtracks/JKJamTracksArtistLandingDev';
|
||||
|
||||
|
|
@ -22,7 +23,8 @@ const JKPublicRoutes = ({ match: { url } }) => (
|
|||
<Route path={`${url}/knowledge-base`} component={JKKnowledgeBase} />
|
||||
<Route path={`${url}/help-desk`} component={JKHelpDesk} />
|
||||
<Route path={`${url}/forum`} component={JKForum} />
|
||||
<Route path={`${url}/unsubscribe`} exact component={JKUnsubscribe} />
|
||||
{/* <Route path={`${url}/unsubscribe/:tok`} exact component={JKUnsubscribe} />
|
||||
<Route path={`${url}/confirm-email-change`} exact component={JKConfirmEmailChange} /> */}
|
||||
<Route path={`${url}/downloads`} exact component={JKDownloads} />
|
||||
<Route path={`${url}/downloads-legacy`} exact component={JKDownloadsLegacy} />
|
||||
<Route path={`${url}/obs-plugin-download`} exact component={JKObsDownloads} />
|
||||
|
|
|
|||
|
|
@ -19,11 +19,5 @@
|
|||
execute "ALTER TABLE users DROP COLUMN profile_complete_reminder3_sent_at"
|
||||
end
|
||||
end
|
||||
=begin
|
||||
ALTER TABLE users ADD COLUMN profile_completed_at TIMESTAMP;
|
||||
CREATE INDEX index_users_on_profile_completed_at ON users USING btree (profile_completed_at);
|
||||
ALTER TABLE users ADD COLUMN profile_complete_reminder1_sent_at TIMESTAMP;
|
||||
ALTER TABLE users ADD COLUMN profile_complete_reminder2_sent_at TIMESTAMP;
|
||||
ALTER TABLE users ADD COLUMN profile_complete_reminder3_sent_at TIMESTAMP;
|
||||
UPDATE users set profile_completed_at=NOW() WHERE users.id IN (SELECT player_id FROM musicians_instruments) OR users.id IN (SELECT player_id FROM genre_players);
|
||||
end
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
# affiliate_quarterly_payments.subscription_due_amount_in_cents
|
||||
# affiliate_monthly_payments.jamtrack_due_amount_in_cents
|
||||
# affiliate_monthly_payments.subscription_due_amount_in_cents
|
||||
class AddTrackingTotalsToAffiliatePartners < ActiveRecord::Migration
|
||||
class AffiliateTrackingTotals < ActiveRecord::Migration
|
||||
|
||||
def self.up
|
||||
execute "ALTER TABLE affiliate_partners ADD COLUMN jamtrack_cumulative_earnings_in_cents INTEGER NOT NULL DEFAULT 0"
|
||||
|
|
@ -16,16 +16,16 @@ class AddTrackingTotalsToAffiliatePartners < ActiveRecord::Migration
|
|||
execute "ALTER TABLE affiliate_partners ADD COLUMN subscriptions_current_quarter_in_cents INTEGER NOT NULL DEFAULT 0"
|
||||
execute "ALTER TABLE affiliate_partners ADD COLUMN jamtracks_sold INTEGER NOT NULL DEFAULT 0"
|
||||
execute "ALTER TABLE affiliate_partners ADD COLUMN subscriptions_count INTEGER NOT NULL DEFAULT 0"
|
||||
execute "ALTER TABLE affiliate_quarterly_payments ADD COLUMN subscriptions_count INTEGER NOT NULL DEFAULT 0"
|
||||
execute "ALTER TABLE affiliate_monthy_payments ADD COLUMN subscriptions_count INTEGER NOT NULL DEFAULT 0"
|
||||
execute "ALTER TABLE affiliate_quarterly_payments ADD COLUMN jamtrack_due_amount_in_cents INTEGER NOT NULL DEFAULT 0"
|
||||
execute "ALTER TABLE affiliate_quarterly_payments ADD COLUMN subscription_due_amount_in_cents INTEGER NOT NULL DEFAULT 0"
|
||||
execute "ALTER TABLE affiliate_monthly_payments ADD COLUMN jamtrack_due_amount_in_cents INTEGER NOT NULL DEFAULT 0"
|
||||
execute "ALTER TABLE affiliate_monthly_payments ADD COLUMN subscription_due_amount_in_cents INTEGER NOT NULL DEFAULT 0"
|
||||
# execute "ALTER TABLE affiliate_quarterly_payments ADD COLUMN subscriptions_count INTEGER NOT NULL DEFAULT 0"
|
||||
# execute "ALTER TABLE affiliate_monthy_payments ADD COLUMN subscriptions_count INTEGER NOT NULL DEFAULT 0"
|
||||
# execute "ALTER TABLE affiliate_quarterly_payments ADD COLUMN jamtrack_due_amount_in_cents INTEGER NOT NULL DEFAULT 0"
|
||||
# execute "ALTER TABLE affiliate_quarterly_payments ADD COLUMN subscription_due_amount_in_cents INTEGER NOT NULL DEFAULT 0"
|
||||
#execute "ALTER TABLE affiliate_monthly_payments ADD COLUMN jamtrack_due_amount_in_cents INTEGER NOT NULL DEFAULT 0"
|
||||
#execute "ALTER TABLE affiliate_monthly_payments ADD COLUMN subscription_due_amount_in_cents INTEGER NOT NULL DEFAULT 0"
|
||||
execute "CREATE INDEX affiliate_partner_user_id_idx ON affiliate_partners USING btree (partner_user_id);"
|
||||
execute "CREATE INDEX affiliate_quarterly_payments_closed_index ON affiliate_quarterly_payments USING btree (paid);"
|
||||
execute "CREATE INDEX affiliate_quarterly_payments_paid_index ON affiliate_quarterly_payments USING btree (closed);"
|
||||
execute "CREATE INDEX affiliate_monthly_payments_paid_index ON affiliate_monthly_payments USING btree (closed);"
|
||||
# execute "CREATE INDEX affiliate_quarterly_payments_closed_index ON affiliate_quarterly_payments USING btree (paid);"
|
||||
# execute "CREATE INDEX affiliate_quarterly_payments_paid_index ON affiliate_quarterly_payments USING btree (closed);"
|
||||
# execute "CREATE INDEX affiliate_monthly_payments_paid_index ON affiliate_monthly_payments USING btree (closed);"
|
||||
|
||||
end
|
||||
=begin
|
||||
|
|
@ -54,7 +54,7 @@ class AddTrackingTotalsToAffiliatePartners < ActiveRecord::Migration
|
|||
execute "ALTER TABLE affiliate_partners DROP COLUMN jamtrack_cumulative_earnings_in_cents"
|
||||
execute "ALTER TABLE affiliate_partners DROP COLUMN subscriptions_cumulative_earnings_in_cents"
|
||||
execute "ALTER TABLE affiliate_partners DROP COLUMN subscriptions_count"
|
||||
execute "ALTER TABLE affiliate_quarterly_payments DROP COLUMN jamtrack_due_amount_in_cents"
|
||||
execute "ALTER TABLE affiliate_quarterly_payments DROP COLUMN subscription_due_amount_in_cents"
|
||||
# execute "ALTER TABLE affiliate_quarterly_payments DROP COLUMN jamtrack_due_amount_in_cents"
|
||||
# execute "ALTER TABLE affiliate_quarterly_payments DROP COLUMN subscription_due_amount_in_cents"
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -11,10 +11,3 @@
|
|||
execute "ALTER TABLE users DROP COLUMN gear_setup_reminder3_sent_at"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
=begin
|
||||
ALTER TABLE users ADD COLUMN gear_setup_reminder1_sent_at TIMESTAMP;
|
||||
ALTER TABLE users ADD COLUMN gear_setup_reminder2_sent_at TIMESTAMP;
|
||||
ALTER TABLE users ADD COLUMN gear_setup_reminder3_sent_at TIMESTAMP;
|
||||
end
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
class AddTestGearReminderColumnsToUsers < ActiveRecord::Migration
|
||||
def self.up
|
||||
execute "ALTER TABLE users ADD COLUMN test_gear_reminder1_sent_at TIMESTAMP"
|
||||
execute "ALTER TABLE users ADD COLUMN test_gear_reminder2_sent_at TIMESTAMP"
|
||||
execute "ALTER TABLE users ADD COLUMN test_gear_reminder3_sent_at TIMESTAMP"
|
||||
end
|
||||
|
||||
def self.down
|
||||
execute "ALTER TABLE users DROP COLUMN test_gear_reminder1_sent_at"
|
||||
execute "ALTER TABLE users DROP COLUMN test_gear_reminder2_sent_at"
|
||||
execute "ALTER TABLE users DROP COLUMN test_gear_reminder3_sent_at"
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
class AddGroupSessionReminderColumnsToUsers < ActiveRecord::Migration
|
||||
def self.up
|
||||
execute "ALTER TABLE users ADD COLUMN group_session_reminder1_sent_at TIMESTAMP"
|
||||
execute "ALTER TABLE users ADD COLUMN group_session_reminder2_sent_at TIMESTAMP"
|
||||
execute "ALTER TABLE users ADD COLUMN group_session_reminder3_sent_at TIMESTAMP"
|
||||
end
|
||||
def self.down
|
||||
execute "ALTER TABLE users DROP COLUMN group_session_reminder1_sent_at"
|
||||
execute "ALTER TABLE users DROP COLUMN group_session_reminder2_sent_at"
|
||||
execute "ALTER TABLE users DROP COLUMN group_session_reminder3_sent_at"
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
class AddTrailExpiresReminderColumnsToUsers < ActiveRecord::Migration
|
||||
def self.up
|
||||
execute "ALTER TABLE users ADD COLUMN trial_expires_reminder1_sent_at TIMESTAMP"
|
||||
execute "ALTER TABLE users ADD COLUMN trial_expires_reminder2_sent_at TIMESTAMP"
|
||||
execute "ALTER TABLE users ADD COLUMN trial_expires_reminder3_sent_at TIMESTAMP"
|
||||
|
||||
# slide in some more production indexes
|
||||
execute "CREATE INDEX index_users_on_first_music_session_at ON users USING btree (first_music_session_at)"
|
||||
# subscription_sync_code
|
||||
execute "CREATE INDEX index_users_on_subscription_sync_code ON users USING btree (subscription_sync_code)"
|
||||
# first_certified_gear_at
|
||||
execute "CREATE INDEX index_users_on_first_certified_gear_at ON users USING btree (first_certified_gear_at)"
|
||||
end
|
||||
def self.down
|
||||
execute "ALTER TABLE users DROP COLUMN trial_expires_reminder1_sent_at"
|
||||
execute "ALTER TABLE users DROP COLUMN trial_expires_reminder2_sent_at"
|
||||
execute "ALTER TABLE users DROP COLUMN trial_expires_reminder3_sent_at"
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
class AddFacebookTrackingToUsers < ActiveRecord::Migration
|
||||
def up
|
||||
execute "ALTER TABLE users ADD COLUMN facebook_click_id varchar(1000)"
|
||||
execute "ALTER TABLE users ADD COLUMN facebook_browser_id varchar(1000)"
|
||||
end
|
||||
|
||||
def down
|
||||
execute "ALTER TABLE users DROP COLUMN facebook_click_id"
|
||||
execute "ALTER TABLE users DROP COLUMN facebook_browser_id"
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
class AddExtendedUtmToUsers < ActiveRecord::Migration[5.0]
|
||||
def up
|
||||
execute <<-SQL
|
||||
ALTER TABLE users ADD COLUMN origin_id character varying;
|
||||
ALTER TABLE users ADD COLUMN origin_term character varying;
|
||||
ALTER TABLE users ADD COLUMN origin_content character varying;
|
||||
|
||||
CREATE INDEX index_users_on_origin_id ON users (origin_id);
|
||||
CREATE INDEX index_users_on_origin_term ON users (origin_term);
|
||||
CREATE INDEX index_users_on_origin_content ON users (origin_content);
|
||||
|
||||
CREATE INDEX index_users_on_origin_utm_source ON users (origin_utm_source);
|
||||
CREATE INDEX index_users_on_origin_utm_medium ON users (origin_utm_medium);
|
||||
SQL
|
||||
end
|
||||
|
||||
def down
|
||||
execute <<-SQL
|
||||
DROP INDEX IF EXISTS index_users_on_origin_utm_medium;
|
||||
DROP INDEX IF EXISTS index_users_on_origin_utm_source;
|
||||
|
||||
DROP INDEX IF EXISTS index_users_on_origin_content;
|
||||
DROP INDEX IF EXISTS index_users_on_origin_term;
|
||||
DROP INDEX IF EXISTS index_users_on_origin_id;
|
||||
|
||||
ALTER TABLE users DROP COLUMN IF EXISTS origin_content;
|
||||
ALTER TABLE users DROP COLUMN IF EXISTS origin_term;
|
||||
ALTER TABLE users DROP COLUMN IF EXISTS origin_id;
|
||||
SQL
|
||||
end
|
||||
end
|
||||
|
|
@ -56,6 +56,7 @@ require "jam_ruby/lib/em_helper"
|
|||
require "jam_ruby/lib/nav"
|
||||
require "jam_ruby/lib/html_sanitize"
|
||||
require "jam_ruby/lib/guitar_center"
|
||||
require "jam_ruby/lib/capi_transmitter"
|
||||
require "jam_ruby/subscription_definitions"
|
||||
require "jam_ruby/resque/resque_jam_error"
|
||||
require "jam_ruby/resque/resque_hooks"
|
||||
|
|
@ -123,6 +124,9 @@ require "jam_ruby/lib/musician_filter"
|
|||
require "jam_ruby/lib/email_profile_reminder"
|
||||
require "jam_ruby/lib/email_signup_survey"
|
||||
require "jam_ruby/lib/gear_setup_reminder"
|
||||
require "jam_ruby/lib/test_gear_reminder"
|
||||
require "jam_ruby/lib/group_session_reminder"
|
||||
require "jam_ruby/lib/trial_expires_reminder"
|
||||
require "jam_ruby/amqp/amqp_connection_manager"
|
||||
require "jam_ruby/database"
|
||||
require "jam_ruby/message_factory"
|
||||
|
|
|
|||
|
|
@ -436,7 +436,7 @@ module JamRuby
|
|||
sendgrid_substitute('@USERID', [user.id])
|
||||
sendgrid_unique_args :type => "profile_complete_reminder2"
|
||||
|
||||
mail(:to => user.email, :subject => "Take 2 minutes to fill out your JamKazam profile now") do |format|
|
||||
mail(:to => user.email, :subject => I18n.t('user_mailer.profile_complete_reminder2.subject')) do |format|
|
||||
format.text
|
||||
format.html { render layout: "user_mailer_beta" }
|
||||
end
|
||||
|
|
@ -448,7 +448,7 @@ module JamRuby
|
|||
sendgrid_substitute('@USERID', [user.id])
|
||||
sendgrid_unique_args :type => "profile_complete_reminder3"
|
||||
|
||||
mail(:to => user.email, :subject => "Last reminder to update your JamKazam profile") do |format|
|
||||
mail(:to => user.email, :subject => I18n.t('user_mailer.profile_complete_reminder3.subject')) do |format|
|
||||
format.text
|
||||
format.html { render layout: "user_mailer_beta" }
|
||||
end
|
||||
|
|
@ -480,6 +480,78 @@ module JamRuby
|
|||
end
|
||||
end
|
||||
|
||||
def test_gear_reminder1(user)
|
||||
@user = user
|
||||
mail(:to => user.email, :subject => I18n.t('user_mailer.test_gear_reminder1.subject')) do |format|
|
||||
format.text
|
||||
format.html { render layout: "user_mailer_beta" }
|
||||
end
|
||||
end
|
||||
|
||||
def test_gear_reminder2(user)
|
||||
@user = user
|
||||
mail(:to => user.email, :subject => I18n.t('user_mailer.test_gear_reminder2.subject')) do |format|
|
||||
format.text
|
||||
format.html { render layout: "user_mailer_beta" }
|
||||
end
|
||||
end
|
||||
|
||||
def test_gear_reminder3(user)
|
||||
@user = user
|
||||
mail(:to => user.email, :subject => I18n.t('user_mailer.test_gear_reminder3.subject')) do |format|
|
||||
format.text
|
||||
format.html { render layout: "user_mailer_beta" }
|
||||
end
|
||||
end
|
||||
|
||||
def group_session_reminder1(user)
|
||||
@user = user
|
||||
mail(:to => user.email, :subject => I18n.t('user_mailer.group_session_reminder1.subject')) do |format|
|
||||
format.text
|
||||
format.html { render layout: "user_mailer_beta" }
|
||||
end
|
||||
end
|
||||
|
||||
def group_session_reminder2(user)
|
||||
@user = user
|
||||
mail(:to => user.email, :subject => I18n.t('user_mailer.group_session_reminder2.subject')) do |format|
|
||||
format.text
|
||||
format.html { render layout: "user_mailer_beta" }
|
||||
end
|
||||
end
|
||||
|
||||
def group_session_reminder3(user)
|
||||
@user = user
|
||||
mail(:to => user.email, :subject => I18n.t('user_mailer.group_session_reminder3.subject')) do |format|
|
||||
format.text
|
||||
format.html { render layout: "user_mailer_beta" }
|
||||
end
|
||||
end
|
||||
|
||||
def trial_expires_reminder1(user)
|
||||
@user = user
|
||||
mail(:to => user.email, :subject => I18n.t('user_mailer.trial_expires_reminder1.subject')) do |format|
|
||||
format.text
|
||||
format.html { render layout: "user_mailer_beta" }
|
||||
end
|
||||
end
|
||||
|
||||
def trial_expires_reminder2(user)
|
||||
@user = user
|
||||
mail(:to => user.email, :subject => I18n.t('user_mailer.trial_expires_reminder2.subject')) do |format|
|
||||
format.text
|
||||
format.html { render layout: "user_mailer_beta" }
|
||||
end
|
||||
end
|
||||
|
||||
def trial_expires_reminder3(user)
|
||||
@user = user
|
||||
mail(:to => user.email, :subject => I18n.t('user_mailer.trial_expires_reminder3.subject')) do |format|
|
||||
format.text
|
||||
format.html { render layout: "user_mailer_beta" }
|
||||
end
|
||||
end
|
||||
|
||||
def signup_survey(user)
|
||||
@user = user
|
||||
@subject = I18n.t('user_mailer.signup_survey.subject')
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
<p><%=I18n.t('user_mailer.group_session_reminder1.greeting') -%> <%= @user.first_name -%> -</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.group_session_reminder1.paragraph1').html_safe -%>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<h2><%=I18n.t('user_mailer.group_session_reminder1.office_hours') -%></h2>
|
||||
<%=I18n.t('user_mailer.group_session_reminder1.paragraph2').html_safe -%>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<h2><%=I18n.t('user_mailer.group_session_reminder1.hosted_sessions') -%></h2>
|
||||
<%=I18n.t('user_mailer.group_session_reminder1.paragraph3').html_safe -%>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<h2><%=I18n.t('user_mailer.group_session_reminder1.inviting_your_friends') -%></h2>
|
||||
<%=I18n.t('user_mailer.group_session_reminder1.paragraph4').html_safe -%>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<h2><%=I18n.t('user_mailer.group_session_reminder1.finding_good_matches') -%></h2>
|
||||
<%=I18n.t('user_mailer.group_session_reminder1.paragraph5').html_safe -%>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<h2><%=I18n.t('user_mailer.group_session_reminder1.scheduling_sessions') -%></h2>
|
||||
<%=I18n.t('user_mailer.group_session_reminder1.paragraph6').html_safe -%>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<h2><%=I18n.t('user_mailer.group_session_reminder1.asking_for_help') -%></h2>
|
||||
<%=I18n.t('user_mailer.group_session_reminder1.paragraph7').html_safe -%>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.group_session_reminder1.paragraph8').html_safe -%>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.group_session_reminder1.regards') -%>,<br />
|
||||
<%=I18n.t('user_mailer.group_session_reminder1.signature') -%>
|
||||
</p>
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<%=I18n.t('user_mailer.group_session_reminder1.greeting') -%> <%= @user.first_name -%> -
|
||||
|
||||
<%=I18n.t('user_mailer.group_session_reminder1.paragraph1') -%>
|
||||
|
||||
<%=I18n.t('user_mailer.group_session_reminder1.office_hours') -%>
|
||||
<%=I18n.t('user_mailer.group_session_reminder1.paragraph2') -%>
|
||||
|
||||
<%=I18n.t('user_mailer.group_session_reminder1.hosted_sessions') -%>
|
||||
<%=I18n.t('user_mailer.group_session_reminder1.paragraph3') -%>
|
||||
|
||||
<%=I18n.t('user_mailer.group_session_reminder1.inviting_your_friends') -%>
|
||||
<%=I18n.t('user_mailer.group_session_reminder1.paragraph4') -%>
|
||||
|
||||
<%=I18n.t('user_mailer.group_session_reminder1.finding_good_matches') -%>
|
||||
<%=I18n.t('user_mailer.group_session_reminder1.paragraph5') -%>
|
||||
|
||||
<%=I18n.t('user_mailer.group_session_reminder1.scheduling_sessions') -%>
|
||||
<%=I18n.t('user_mailer.group_session_reminder1.paragraph6') -%>
|
||||
|
||||
<%=I18n.t('user_mailer.group_session_reminder1.asking_for_help') -%>
|
||||
<%=I18n.t('user_mailer.group_session_reminder1.paragraph7') -%>
|
||||
|
||||
<%=I18n.t('user_mailer.group_session_reminder1.paragraph8') -%>
|
||||
|
||||
<%=I18n.t('user_mailer.group_session_reminder1.regards') -%>,
|
||||
<%=I18n.t('user_mailer.group_session_reminder1.signature') -%>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<p><%=I18n.t('user_mailer.group_session_reminder2.greeting') -%> <%= @user.first_name -%> -</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.group_session_reminder2.paragraph1').html_safe -%>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.group_session_reminder2.paragraph2').html_safe -%>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.group_session_reminder2.paragraph3').html_safe -%>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.group_session_reminder2.regards') -%>,<br />
|
||||
<%=I18n.t('user_mailer.group_session_reminder2.signature') -%>
|
||||
</p>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<%=I18n.t('user_mailer.group_session_reminder2.greeting') -%> <%= @user.first_name -%> -
|
||||
|
||||
<%=I18n.t('user_mailer.group_session_reminder2.paragraph1') -%>
|
||||
|
||||
<%=I18n.t('user_mailer.group_session_reminder2.paragraph2') -%>
|
||||
|
||||
<%=I18n.t('user_mailer.group_session_reminder2.paragraph3') -%>
|
||||
|
||||
<%=I18n.t('user_mailer.group_session_reminder2.regards') -%>,
|
||||
<%=I18n.t('user_mailer.group_session_reminder2.signature') -%>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<%= I18n.t('user_mailer.group_session_reminder3.greeting') -%> <%= @user.first_name -%> -
|
||||
|
||||
<%= I18n.t('user_mailer.group_session_reminder3.paragraph1') -%>
|
||||
|
||||
<%= I18n.t('user_mailer.group_session_reminder3.paragraph2') -%>
|
||||
|
||||
<%= I18n.t('user_mailer.group_session_reminder3.paragraph3') -%>
|
||||
|
||||
<%= I18n.t('user_mailer.group_session_reminder3.regards') -%>,
|
||||
<%= I18n.t('user_mailer.group_session_reminder3.signature') -%>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<%=I18n.t('user_mailer.group_session_reminder3.greeting') -%> <%= @user.first_name -%> -
|
||||
|
||||
<%=I18n.t('user_mailer.group_session_reminder3.paragraph1') -%>
|
||||
|
||||
<%=I18n.t('user_mailer.group_session_reminder3.paragraph2') -%>
|
||||
|
||||
<%=I18n.t('user_mailer.group_session_reminder3.paragraph3') -%>
|
||||
|
||||
<%=I18n.t('user_mailer.group_session_reminder3.regards') -%>,
|
||||
<%=I18n.t('user_mailer.group_session_reminder3.signature') -%>
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<p><%=I18n.t('user_mailer.test_gear_reminder1.greeting') -%> <%= @user.first_name -%> -</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.test_gear_reminder1.paragraph1').html_safe -%>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.test_gear_reminder1.paragraph2').html_safe -%>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.test_gear_reminder1.paragraph3').html_safe -%>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.test_gear_reminder1.paragraph4').html_safe -%>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.test_gear_reminder1.paragraph5').html_safe -%>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.test_gear_reminder1.regards') -%>,<br />
|
||||
<%=I18n.t('user_mailer.test_gear_reminder1.signature') -%>
|
||||
</p>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<%=I18n.t('user_mailer.test_gear_reminder1.greeting') -%> <%= @user.first_name -%> -
|
||||
|
||||
<%=I18n.t('user_mailer.test_gear_reminder1.paragraph1').html_safe -%>
|
||||
|
||||
<%=I18n.t('user_mailer.test_gear_reminder1.paragraph2').html_safe -%>
|
||||
|
||||
<%=I18n.t('user_mailer.test_gear_reminder1.paragraph3').html_safe -%>
|
||||
|
||||
<%=I18n.t('user_mailer.test_gear_reminder1.paragraph4').html_safe -%>
|
||||
|
||||
<%=I18n.t('user_mailer.test_gear_reminder1.paragraph5').html_safe -%>
|
||||
|
||||
<%=I18n.t('user_mailer.test_gear_reminder1.regards') -%>,
|
||||
<%=I18n.t('user_mailer.test_gear_reminder1.signature') -%>
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<p><%=I18n.t('user_mailer.test_gear_reminder2.greeting') -%> <%= @user.first_name -%> -</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.test_gear_reminder2.paragraph1').html_safe -%>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.test_gear_reminder2.paragraph2').html_safe -%>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.test_gear_reminder2.paragraph3').html_safe -%>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.test_gear_reminder2.paragraph4').html_safe -%>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.test_gear_reminder2.regards') -%>,<br />
|
||||
<%=I18n.t('user_mailer.test_gear_reminder2.signature') -%>
|
||||
</p>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<%=I18n.t('user_mailer.test_gear_reminder2.greeting') -%> <%= @user.first_name -%> -
|
||||
|
||||
<%=I18n.t('user_mailer.test_gear_reminder2.paragraph1').html_safe -%>
|
||||
|
||||
<%=I18n.t('user_mailer.test_gear_reminder2.paragraph2').html_safe -%>
|
||||
|
||||
<%=I18n.t('user_mailer.test_gear_reminder2.paragraph3').html_safe -%>
|
||||
|
||||
<%=I18n.t('user_mailer.test_gear_reminder2.paragraph4').html_safe -%>
|
||||
|
||||
<%=I18n.t('user_mailer.test_gear_reminder2.regards') -%>,
|
||||
<%=I18n.t('user_mailer.test_gear_reminder2.signature') -%>
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<p><%=I18n.t('user_mailer.test_gear_reminder3.greeting') -%> <%= @user.first_name -%> -</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.test_gear_reminder3.paragraph1').html_safe -%>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.test_gear_reminder3.paragraph2').html_safe -%>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.test_gear_reminder3.paragraph3').html_safe -%>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.test_gear_reminder3.paragraph4').html_safe -%>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.test_gear_reminder3.paragraph5').html_safe -%>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.test_gear_reminder3.regards') -%>,<br />
|
||||
<%=I18n.t('user_mailer.test_gear_reminder3.signature') -%>
|
||||
</p>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<%=I18n.t('user_mailer.test_gear_reminder1.greeting') -%> <%= @user.first_name -%> -
|
||||
|
||||
<%=I18n.t('user_mailer.test_gear_reminder1.paragraph1').html_safe -%>
|
||||
|
||||
<%=I18n.t('user_mailer.test_gear_reminder1.paragraph2').html_safe -%>
|
||||
|
||||
<%=I18n.t('user_mailer.test_gear_reminder1.paragraph3').html_safe -%>
|
||||
|
||||
<%=I18n.t('user_mailer.test_gear_reminder1.paragraph4').html_safe -%>
|
||||
|
||||
<%=I18n.t('user_mailer.test_gear_reminder1.paragraph5').html_safe -%>
|
||||
|
||||
<%=I18n.t('user_mailer.test_gear_reminder1.regards') -%>,
|
||||
<%=I18n.t('user_mailer.test_gear_reminder1.signature') -%>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<p><%=I18n.t('user_mailer.trial_expires_reminder1.greeting') -%> <%= @user.first_name -%> -</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.trial_expires_reminder1.paragraph1').html_safe -%>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.trial_expires_reminder1.paragraph2').html_safe -%>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.trial_expires_reminder1.regards') -%>,<br />
|
||||
<%=I18n.t('user_mailer.trial_expires_reminder1.signature') -%>
|
||||
</p>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<%=I18n.t('user_mailer.trial_expires_reminder1.greeting') -%> <%= @user.first_name -%> -
|
||||
|
||||
<%=I18n.t('user_mailer.trial_expires_reminder1.paragraph1') -%>
|
||||
|
||||
<%=I18n.t('user_mailer.trial_expires_reminder1.paragraph2') -%>
|
||||
|
||||
<%=I18n.t('user_mailer.trial_expires_reminder1.regards') -%>,
|
||||
<%=I18n.t('user_mailer.trial_expires_reminder1.signature') -%>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<p><%=I18n.t('user_mailer.trial_expires_reminder2.greeting') -%> <%= @user.first_name -%> -</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.trial_expires_reminder2.paragraph1').html_safe -%>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.trial_expires_reminder2.paragraph2').html_safe -%>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.trial_expires_reminder2.paragraph3').html_safe -%>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.trial_expires_reminder2.regards') -%>,<br />
|
||||
<%=I18n.t('user_mailer.trial_expires_reminder2.signature') -%>
|
||||
</p>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<%=I18n.t('user_mailer.trial_expires_reminder2.greeting') -%> <%= @user.first_name -%> -
|
||||
|
||||
<%=I18n.t('user_mailer.trial_expires_reminder2.paragraph1') -%>
|
||||
|
||||
<%=I18n.t('user_mailer.trial_expires_reminder2.paragraph2') -%>
|
||||
|
||||
<%=I18n.t('user_mailer.trial_expires_reminder2.paragraph3') -%>
|
||||
|
||||
<%=I18n.t('user_mailer.trial_expires_reminder2.regards') -%>,
|
||||
<%=I18n.t('user_mailer.trial_expires_reminder2.signature') -%>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<p><%=I18n.t('user_mailer.trial_expires_reminder3.greeting') -%> <%= @user.first_name -%> -</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.trial_expires_reminder3.paragraph1').html_safe -%>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.trial_expires_reminder3.paragraph2').html_safe -%>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.trial_expires_reminder3.paragraph3').html_safe -%>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<%=I18n.t('user_mailer.trial_expires_reminder3.regards') -%>,<br />
|
||||
<%=I18n.t('user_mailer.trial_expires_reminder3.signature') -%>
|
||||
</p>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<%=I18n.t('user_mailer.trial_expires_reminder3.greeting') -%> <%= @user.first_name -%> -
|
||||
|
||||
<%=I18n.t('user_mailer.trial_expires_reminder3.paragraph1') -%>
|
||||
|
||||
<%=I18n.t('user_mailer.trial_expires_reminder3.paragraph2') -%>
|
||||
|
||||
<%=I18n.t('user_mailer.trial_expires_reminder3.paragraph3') -%>
|
||||
|
||||
<%=I18n.t('user_mailer.trial_expires_reminder3.regards') -%>,
|
||||
<%=I18n.t('user_mailer.trial_expires_reminder3.signature') -%>
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
<p>
|
||||
<%= I18n.t "mailer_layout.footer.paragraph1" -%> <a href="https://www.jamkazam.com" target="_blank">JamKazam</a>. <br /> <%= I18n.t "mailer_layout.footer.you_can" -%> <a
|
||||
|
||||
href="https://www.jamkazam.com/unsubscribe/<%= @user.unsubscribe_token %>"
|
||||
href="<%= ApplicationHelper.spa_base_uri %>/unsubscribe/<%= @user.unsubscribe_token %>"
|
||||
style="
|
||||
color: #2c7be5;
|
||||
text-decoration: none;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
require 'net/http'
|
||||
require 'uri'
|
||||
require 'json'
|
||||
|
||||
class CapiTransmitter
|
||||
|
||||
def self.send_event(event_name, user, custom_data = {}, event_source_url = nil, test_event_code = nil)
|
||||
begin
|
||||
puts("CapiTransmitter: Sending event #{event_name} for User #{user.id}")
|
||||
pixel_id = APP_CONFIG.facebook_pixel_id
|
||||
access_token = APP_CONFIG.facebook_access_token
|
||||
|
||||
if pixel_id.blank? || access_token.blank?
|
||||
puts("CapiTransmitter: Missing Facebook Pixel ID or Access Token. Skipping event #{event_name}.")
|
||||
return
|
||||
end
|
||||
|
||||
# Construct the User Data object
|
||||
user_data = {
|
||||
# client_ip_address: user.current_sign_in_ip,
|
||||
# client_user_agent: user.user_agent # Note: User model might not have user_agent stored directly, might need to pass it or rely on what's available
|
||||
}
|
||||
|
||||
if !user.facebook_click_id.present? ||
|
||||
!APP_CONFIG.facebook_ad_source.include?(user.origin_utm_source)
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
# Enhance user data with hashed PII if available
|
||||
# Facebook requires SHA256 hashing for PII
|
||||
# For now, we rely on click_id and browser_id as primary keys if available
|
||||
user_data[:fbc] = user.facebook_click_id if user.facebook_click_id.present?
|
||||
user_data[:fbp] = user.facebook_browser_id if user.facebook_browser_id.present?
|
||||
|
||||
# If we have email/phone, we should hash them. But for now, let's stick to the IDs.
|
||||
# Ideally we should hash email if we have it.
|
||||
if user.email.present?
|
||||
user_data[:em] = Digest::SHA256.hexdigest(user.email.downcase)
|
||||
end
|
||||
|
||||
payload = {
|
||||
data: [
|
||||
{
|
||||
event_name: event_name,
|
||||
event_time: Time.now.to_i,
|
||||
action_source: "website",
|
||||
user_data: user_data,
|
||||
custom_data: custom_data,
|
||||
event_source_url: event_source_url
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
if test_event_code.present?
|
||||
payload[:test_event_code] = test_event_code
|
||||
end
|
||||
|
||||
url = URI.parse("https://graph.facebook.com/v19.0/#{pixel_id}/events?access_token=#{access_token}")
|
||||
|
||||
http = Net::HTTP.new(url.host, url.port)
|
||||
|
||||
if APP_CONFIG.facebook_conversion_api_tls == false
|
||||
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
||||
else
|
||||
http.use_ssl = true
|
||||
end
|
||||
|
||||
request = Net::HTTP::Post.new(url.request_uri)
|
||||
request['Content-Type'] = 'application/json'
|
||||
request.body = payload.to_json
|
||||
|
||||
response = http.request(request)
|
||||
|
||||
if response.code.to_i >= 400
|
||||
puts("CapiTransmitter Error: #{response.code} - #{response.body}")
|
||||
else
|
||||
puts("CapiTransmitter Success: #{event_name} sent for User #{user.id}")
|
||||
end
|
||||
|
||||
rescue => e
|
||||
puts("CapiTransmitter Exception: #{e.message}")
|
||||
puts(e.backtrace.join("\n"))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -4,20 +4,22 @@ module JamRuby
|
|||
|
||||
def self.send_reminders
|
||||
begin
|
||||
cutoff_date = Date.parse(Rails.application.config.profile_complete_reminders_effective_from_date) # Define a cutoff date for the profile completion emails
|
||||
#If the user has not updated their profile 1 day after signup, then send reminder email1
|
||||
reminder1_users.find_each do |user|
|
||||
reminder1_users(cutoff_date).find_each do |user|
|
||||
puts "reminder1_users user: #{user.id}"
|
||||
UserMailer.profile_complete_reminder1(user).deliver_now
|
||||
User.where(id: user.id).update_all(profile_complete_reminder1_sent_at: Time.now)
|
||||
end
|
||||
|
||||
#If the user has not updated their profile 3 days after signup, then send reminder email2
|
||||
reminder2_users.find_each do |user|
|
||||
reminder2_users(cutoff_date).find_each do |user|
|
||||
UserMailer.profile_complete_reminder2(user).deliver_now
|
||||
User.where(id: user.id).update_all(profile_complete_reminder2_sent_at: Time.now)
|
||||
end
|
||||
|
||||
#If the user has not updated their profile 5 days after signup, then send reminder email3
|
||||
reminder3_users.find_each do |user|
|
||||
reminder3_users(cutoff_date).find_each do |user|
|
||||
UserMailer.profile_complete_reminder3(user).deliver_now
|
||||
User.where(id: user.id).update_all(profile_complete_reminder3_sent_at: Time.now)
|
||||
end
|
||||
|
|
@ -31,18 +33,20 @@ module JamRuby
|
|||
User.where("users.profile_completed_at IS NULL AND users.subscribe_email = ?", true)
|
||||
end
|
||||
|
||||
def self.reminder1_users
|
||||
EmailProfileReminder.prospect_users.where("users.created_at < ? AND users.profile_complete_reminder1_sent_at IS NULL", 1.day.ago)
|
||||
def self.reminder1_users(cutoff_date)
|
||||
# ensure that the user has had the account for at least one day
|
||||
puts "reminder1_users cutoff_date: #{cutoff_date}"
|
||||
EmailProfileReminder.prospect_users.where("users.created_at > ? AND users.profile_complete_reminder1_sent_at IS NULL AND (NOW() - users.created_at) > INTERVAL '1 day'", cutoff_date)
|
||||
end
|
||||
|
||||
def self.reminder2_users
|
||||
EmailProfileReminder.prospect_users.where("users.created_at < ? AND users.profile_complete_reminder1_sent_at IS NOT NULL AND users.profile_complete_reminder2_sent_at IS NULL", 3.days.ago)
|
||||
def self.reminder2_users(cutoff_date)
|
||||
# ensure that the user has had the account for at least three days and guard against rapid back-to-back emails
|
||||
EmailProfileReminder.prospect_users.where("users.created_at > ? AND users.profile_complete_reminder1_sent_at < ? AND users.profile_complete_reminder1_sent_at IS NOT NULL AND users.profile_complete_reminder2_sent_at IS NULL", cutoff_date, 2.days.ago)
|
||||
end
|
||||
|
||||
def self.reminder3_users
|
||||
EmailProfileReminder.prospect_users.where("users.created_at < ? AND users.profile_complete_reminder2_sent_at IS NOT NULL AND users.profile_complete_reminder3_sent_at IS NULL", 5.days.ago)
|
||||
def self.reminder3_users(cutoff_date)
|
||||
# ensure that user has had the profile for 5 days and guard against rapid back-to-back emails
|
||||
EmailProfileReminder.prospect_users.where("users.created_at > ? AND users.profile_complete_reminder2_sent_at < ? AND users.profile_complete_reminder2_sent_at IS NOT NULL AND users.profile_complete_reminder3_sent_at IS NULL", cutoff_date, 2.days.ago)
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -5,42 +5,42 @@ module JamRuby
|
|||
|
||||
def self.send_reminders
|
||||
begin
|
||||
cutoff_date = Date.parse(Rails.application.config.signup_survey_cutoff_date) # Define a cutoff date for the survey/gear setup emails
|
||||
cutoff_date = Date.parse(Rails.application.config.gear_setup_reminders_effective_from_date) # Define a cutoff date for the survey/gear setup emails
|
||||
|
||||
reminder1_users(cutoff_date).find_each do |user|
|
||||
reminder1_users(cutoff_date).find_each(batch_size:100) do |user|
|
||||
UserMailer.gear_setup_reminder1(user).deliver_now
|
||||
User.where(id: user.id).update_all(gear_setup_reminder1_sent_at: Time.now)
|
||||
end
|
||||
|
||||
reminder2_users(cutoff_date).find_each do |user|
|
||||
reminder2_users(cutoff_date).find_each(batch_size:100) do |user|
|
||||
UserMailer.gear_setup_reminder2(user).deliver_now
|
||||
User.where(id: user.id).update_all(gear_setup_reminder2_sent_at: Time.now)
|
||||
end
|
||||
|
||||
reminder3_users(cutoff_date).find_each do |user|
|
||||
reminder3_users(cutoff_date).find_each(batch_size:100) do |user|
|
||||
UserMailer.gear_setup_reminder3(user).deliver_now
|
||||
User.where(id: user.id).update_all(gear_setup_reminder3_sent_at: Time.now)
|
||||
end
|
||||
rescue Exception => e
|
||||
@@log.error("unable to send gear setup reminder email #{e}")
|
||||
puts "unable to send gear setup reminder email #{e}"
|
||||
@@log.error("unable to send GearSetupReminder email #{e}")
|
||||
puts "unable to send GearSetupReminder email #{e}"
|
||||
end
|
||||
end
|
||||
|
||||
def self.prospect_users
|
||||
User.where("users.first_certified_gear_at IS NULL")
|
||||
User.where("users.first_certified_gear_at IS NULL AND users.subscribe_email = ?", true)
|
||||
end
|
||||
|
||||
def self.reminder1_users(cutoff_date)
|
||||
GearSetupReminder.prospect_users.where("users.created_at < ? AND users.created_at > ? AND users.gear_setup_reminder1_sent_at IS NULL", 1.day.ago, cutoff_date)
|
||||
GearSetupReminder.prospect_users.where("users.created_at > ? AND users.gear_setup_reminder1_sent_at IS NULL AND (NOW() - users.created_at) > INTERVAL '1 day'", cutoff_date)
|
||||
end
|
||||
|
||||
def self.reminder2_users(cutoff_date)
|
||||
GearSetupReminder.prospect_users.where("users.created_at < ? AND users.created_at > ? AND users.gear_setup_reminder1_sent_at IS NOT NULL AND users.gear_setup_reminder2_sent_at IS NULL", 3.days.ago, cutoff_date)
|
||||
GearSetupReminder.prospect_users.where("users.created_at > ? AND users.gear_setup_reminder1_sent_at IS NOT NULL AND users.gear_setup_reminder2_sent_at IS NULL AND users.gear_setup_reminder1_sent_at < ?", cutoff_date, 2.days.ago)
|
||||
end
|
||||
|
||||
def self.reminder3_users(cutoff_date)
|
||||
GearSetupReminder.prospect_users.where("users.created_at < ? AND users.created_at > ? AND users.gear_setup_reminder2_sent_at IS NOT NULL AND users.gear_setup_reminder3_sent_at IS NULL", 5.days.ago, cutoff_date)
|
||||
GearSetupReminder.prospect_users.where("users.created_at > ? AND users.gear_setup_reminder2_sent_at IS NOT NULL AND users.gear_setup_reminder3_sent_at IS NULL AND users.gear_setup_reminder2_sent_at < ?", cutoff_date, 2.days.ago)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
module JamRuby
|
||||
class GroupSessionReminder
|
||||
|
||||
@@log = Logging.logger[GroupSessionReminder]
|
||||
|
||||
def self.send_reminders
|
||||
begin
|
||||
cutoff_date = Date.parse(Rails.application.config.group_session_reminders_effective_from_date) # Define a cutoff date for the group session reminder emails
|
||||
|
||||
reminder1_users(cutoff_date).find_each(batch_size:100) do |user|
|
||||
UserMailer.group_session_reminder1(user).deliver_now
|
||||
User.where(id: user.id).update_all(group_session_reminder1_sent_at: Time.now)
|
||||
end
|
||||
|
||||
reminder2_users(cutoff_date).find_each(batch_size:100) do |user|
|
||||
UserMailer.group_session_reminder2(user).deliver_now
|
||||
User.where(id: user.id).update_all(group_session_reminder2_sent_at: Time.now)
|
||||
end
|
||||
|
||||
reminder3_users(cutoff_date).find_each(batch_size:100) do |user|
|
||||
UserMailer.group_session_reminder3(user).deliver_now
|
||||
User.where(id: user.id).update_all(group_session_reminder3_sent_at: Time.now)
|
||||
end
|
||||
rescue Exception => e
|
||||
@@log.error("unable to send GroupSessionReminder email #{e}")
|
||||
puts "unable to send GroupSessionReminder email #{e}"
|
||||
end
|
||||
end
|
||||
|
||||
def self.prospect_users
|
||||
User.where("users.first_certified_gear_at IS NOT NULL AND users.first_real_music_session_at IS NULL")
|
||||
end
|
||||
|
||||
def self.reminder1_users(cutoff_date)
|
||||
GroupSessionReminder.prospect_users.where("users.created_at > ? AND users.first_music_session_at IS NOT NULL AND users.group_session_reminder1_sent_at IS NULL", cutoff_date)
|
||||
end
|
||||
|
||||
def self.reminder2_users(cutoff_date)
|
||||
GroupSessionReminder.prospect_users.where("users.created_at > ? AND users.group_session_reminder1_sent_at IS NOT NULL AND users.group_session_reminder2_sent_at IS NULL AND users.first_music_session_at < ? AND group_session_reminder1_sent_at < ?", cutoff_date, 3.days.ago, 1.day.ago)
|
||||
end
|
||||
|
||||
def self.reminder3_users(cutoff_date)
|
||||
GroupSessionReminder.prospect_users.where("users.created_at > ? AND users.group_session_reminder2_sent_at IS NOT NULL AND users.group_session_reminder3_sent_at IS NULL AND users.first_music_session_at < ? AND group_session_reminder2_sent_at < ?", cutoff_date, 5.days.ago, 1.day.ago)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
module JamRuby
|
||||
class TestGearReminder
|
||||
|
||||
@@log = Logging.logger[TestGearReminder]
|
||||
|
||||
def self.send_reminders
|
||||
begin
|
||||
cutoff_date = Date.parse(Rails.application.config.test_gear_reminders_effective_from_date) # Define a cutoff date for the test gear setup emails
|
||||
reminder1_users(cutoff_date).find_each(batch_size:100) do |user|
|
||||
UserMailer.test_gear_reminder1(user).deliver_now
|
||||
User.where(id: user.id).update_all(test_gear_reminder1_sent_at: Time.now)
|
||||
end
|
||||
|
||||
reminder2_users(cutoff_date).find_each(batch_size:100) do |user|
|
||||
UserMailer.test_gear_reminder2(user).deliver_now
|
||||
User.where(id: user.id).update_all(test_gear_reminder2_sent_at: Time.now)
|
||||
end
|
||||
|
||||
reminder3_users(cutoff_date).find_each(batch_size:100) do |user|
|
||||
UserMailer.test_gear_reminder3(user).deliver_now
|
||||
User.where(id: user.id).update_all(test_gear_reminder3_sent_at: Time.now)
|
||||
end
|
||||
rescue Exception => e
|
||||
@@log.error("unable to send TestGearReminder email #{e}")
|
||||
puts "unable to send TestGearReminder email #{e}"
|
||||
end
|
||||
end
|
||||
|
||||
def self.prospect_users
|
||||
User.where("users.first_music_session_at IS NULL AND first_certified_gear_at IS NOT NULL")
|
||||
end
|
||||
|
||||
def self.reminder1_users(cutoff_date)
|
||||
TestGearReminder.prospect_users.where("users.created_at > ? AND users.test_gear_reminder1_sent_at IS NULL AND (NOW() - users.first_certified_gear_at) > INTERVAL '1 day'", cutoff_date)
|
||||
end
|
||||
|
||||
def self.reminder2_users(cutoff_date)
|
||||
TestGearReminder.prospect_users.where("users.created_at > ? AND users.test_gear_reminder1_sent_at IS NOT NULL AND users.test_gear_reminder2_sent_at IS NULL AND users.test_gear_reminder1_sent_at < ?", cutoff_date, 2.days.ago)
|
||||
end
|
||||
|
||||
def self.reminder3_users(cutoff_date)
|
||||
TestGearReminder.prospect_users.where("users.created_at > ? AND users.test_gear_reminder2_sent_at IS NOT NULL AND users.test_gear_reminder3_sent_at IS NULL AND users.test_gear_reminder2_sent_at < ?", cutoff_date, 2.days.ago)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
# lib/jam_ruby/lib/trial_expires_reminder.rb
|
||||
#
|
||||
# Sends reminder emails after a user's free trial has ended.
|
||||
#
|
||||
# Usage: JamRuby::TrialExpiresReminder.send_reminders
|
||||
#
|
||||
module JamRuby
|
||||
class TrialExpiresReminder
|
||||
@@log = Logging.logger[TrialExpiresReminder]
|
||||
|
||||
FIRST_NOTIFICATION_CHECK = 1.day.ago
|
||||
# unused here, but just as a form of simple documentation...
|
||||
# SECOND_NOTIFICATION_CHECK = 4.days (from the previous reminder)
|
||||
# THIRD_NOTIFICATION_CHECK = 4.days (from the previous reminder)
|
||||
|
||||
def self.prospect_users(cutoff_date)
|
||||
User.where("(users.subscription_trial_ends_at IS NOT NULL AND users.created_at > ?)", cutoff_date)
|
||||
end
|
||||
|
||||
# trial_ended | in_trial
|
||||
def self.reminder1_users(cutoff_date)
|
||||
prospect_users(cutoff_date).where("users.subscription_sync_code = 'trial_ended' AND users.subscription_trial_ends_at < ? AND users.trial_expires_reminder1_sent_at IS NULL", FIRST_NOTIFICATION_CHECK)
|
||||
end
|
||||
|
||||
def self.reminder2_users(cutoff_date)
|
||||
prospect_users(cutoff_date).where("users.subscription_sync_code = 'trial_ended' AND trial_expires_reminder1_sent_at IS NOT NULL AND users.trial_expires_reminder2_sent_at IS NULL AND (NOW() - trial_expires_reminder1_sent_at) > INTERVAL '4 days'")
|
||||
end
|
||||
|
||||
def self.reminder3_users(cutoff_date)
|
||||
prospect_users(cutoff_date).where("users.subscription_sync_code = 'trial_ended' AND trial_expires_reminder2_sent_at IS NOT NULL AND users.trial_expires_reminder3_sent_at IS NULL AND (NOW() - trial_expires_reminder2_sent_at) > INTERVAL '4 days'")
|
||||
end
|
||||
|
||||
def self.send_reminders
|
||||
cod = Date.parse(Rails.application.config.trial_expires_reminders_effective_from_date) # Define a cutoff date for the trial expires reminder emails
|
||||
|
||||
reminder1_users(cod).find_each(batch_size: 100) do |user|
|
||||
begin
|
||||
UserMailer.trial_expires_reminder1(user).deliver_now
|
||||
user.update_attribute(:trial_expires_reminder1_sent_at, Time.now)
|
||||
rescue Exception => e
|
||||
@@log.error("unable to send trial_expires_reminder1 email #{e}")
|
||||
puts "unable to send trial_expires_reminder1 email #{e}"
|
||||
end
|
||||
end
|
||||
reminder2_users(cod).find_each(batch_size: 100) do |user|
|
||||
begin
|
||||
UserMailer.trial_expires_reminder2(user).deliver_now
|
||||
user.update_attribute(:trial_expires_reminder2_sent_at, Time.now)
|
||||
rescue Exception => e
|
||||
@@log.error("unable to send trial_expires_reminder2 email #{e}")
|
||||
puts "unable to send trial_expires_reminder2 email #{e}"
|
||||
end
|
||||
end
|
||||
reminder3_users(cod).find_each(batch_size: 100) do |user|
|
||||
begin
|
||||
UserMailer.trial_expires_reminder3(user).deliver_now
|
||||
user.update_attribute(:trial_expires_reminder3_sent_at, Time.now)
|
||||
rescue Exception => e
|
||||
@@log.error("unable to send trial_expires_reminder3 email #{e}")
|
||||
puts "unable to send trial_expires_reminder3 email #{e}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
|
||||
module JamRuby
|
||||
class Ars < ActiveRecord::Base
|
||||
|
||||
attr_accessible :active, :name, :id_int, :ip, as: :admin
|
||||
attr_accessible :password, :username, :active, :beta, :name, :provider, :id_int, :ip, :port, :continent, :country, :city, :subdivision, :latitude, :longitude, as: :admin
|
||||
|
||||
self.table_name = "arses"
|
||||
@@log = Logging.logger[Ars]
|
||||
|
|
@ -11,4 +10,4 @@ module JamRuby
|
|||
Ars.where(active: true, beta: beta).where('ip is not NULL').where("ip != ''").all
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ module JamRuby
|
|||
after_save :update_teacher_pct
|
||||
|
||||
attr_accessible :first_name, :last_name, :email, :city, :password, :password_confirmation, :state, :country, :birth_date, :subscribe_email, :terms_of_service, :original_fpfile, :cropped_fpfile, :cropped_large_fpfile, :cropped_s3_path, :cropped_large_s3_path, :photo_url, :large_photo_url, :crop_selection, :used_current_month, :used_month_play_time,
|
||||
:v2_photo_url, :v2_photo_uploaded
|
||||
:v2_photo_url, :v2_photo_uploaded, :facebook_click_id, :facebook_browser_id
|
||||
|
||||
# updating_password corresponds to a lost_password
|
||||
attr_accessor :test_drive_packaging, :validate_instruments, :updating_password, :updating_email, :updated_email, :update_email_confirmation_url, :administratively_created, :current_password, :setting_password, :confirm_current_password, :updating_avatar, :updating_progression_field, :mods_json, :expecting_gift_card, :purchase_required, :user_type
|
||||
|
|
@ -542,6 +542,21 @@ module JamRuby
|
|||
@updating_progression_field = true
|
||||
if self[field_name].nil?
|
||||
self[field_name] = time
|
||||
|
||||
# CAPI Hooks
|
||||
begin
|
||||
case field_name.to_s
|
||||
when 'first_ran_client_at'
|
||||
# StartTrial: When user opens the app
|
||||
CapiTransmitter.send_event('StartTrial', self, { value: '0.00', currency: 'USD', predicted_ltv: '0.00' })
|
||||
when 'first_certified_gear_at'
|
||||
# AddToCart: When user finishes Gear Wizard
|
||||
CapiTransmitter.send_event('AddToCart', self)
|
||||
end
|
||||
rescue => e
|
||||
puts("Error sending #{field_name} CAPI event: #{e.message}")
|
||||
end
|
||||
|
||||
self.save
|
||||
end
|
||||
end
|
||||
|
|
@ -1579,6 +1594,8 @@ module JamRuby
|
|||
license_end = options[:license_end]
|
||||
import_source = options[:import_source]
|
||||
desired_plan_code = options[:desired_plan_code]
|
||||
facebook_click_id = options[:facebook_click_id]
|
||||
facebook_browser_id = options[:facebook_browser_id]
|
||||
|
||||
if desired_plan_code == ''
|
||||
desired_plan_code = nil
|
||||
|
|
@ -1640,16 +1657,24 @@ module JamRuby
|
|||
musician = true
|
||||
end
|
||||
user.musician = !!musician
|
||||
user.facebook_click_id = facebook_click_id
|
||||
user.facebook_browser_id = facebook_browser_id
|
||||
|
||||
if origin
|
||||
user.origin_utm_source = origin["utm_source"]
|
||||
user.origin_utm_medium = origin["utm_medium"]
|
||||
user.origin_utm_campaign = origin["utm_campaign"]
|
||||
user.origin_id = origin["utm_id"]
|
||||
user.origin_term = origin["utm_term"]
|
||||
user.origin_content = origin["utm_content"]
|
||||
user.origin_referrer = origin["referrer"]
|
||||
else
|
||||
user.origin_utm_source = 'organic'
|
||||
user.origin_utm_medium = 'organic'
|
||||
user.origin_utm_campaign = nil
|
||||
user.origin_id = nil
|
||||
user.origin_term = nil
|
||||
user.origin_content = nil
|
||||
user.origin_referrer = nil
|
||||
end
|
||||
|
||||
|
|
@ -1865,6 +1890,15 @@ module JamRuby
|
|||
if user.errors.any?
|
||||
raise ActiveRecord::Rollback
|
||||
else
|
||||
# CAPI Hook: CompleteRegistration
|
||||
if user.facebook_click_id.present?
|
||||
begin
|
||||
CapiTransmitter.send_event('CompleteRegistration', user)
|
||||
rescue => e
|
||||
puts("Error sending CompleteRegistration CAPI event: #{e.message}")
|
||||
end
|
||||
end
|
||||
|
||||
# if the partner ID was present and the partner doesn't already have a user associated, associate this new user with the affiliate partner
|
||||
if affiliate_partner && affiliate_partner.partner_user.nil?
|
||||
affiliate_partner.partner_user = user
|
||||
|
|
|
|||
|
|
@ -336,6 +336,33 @@ module JamRuby
|
|||
end
|
||||
end
|
||||
|
||||
def report_meta_capi(current_user, plan_code)
|
||||
# CAPI Hook: Subscribe
|
||||
begin
|
||||
monthly_cost = case plan_code
|
||||
when JamRuby::SubscriptionDefinitions::JAM_SILVER, JamRuby::SubscriptionDefinitions::JAM_SILVER_YEARLY
|
||||
5.00
|
||||
when JamRuby::SubscriptionDefinitions::JAM_GOLD, JamRuby::SubscriptionDefinitions::JAM_GOLD_YEARLY
|
||||
10.00
|
||||
when JamRuby::SubscriptionDefinitions::JAM_PLATINUM, JamRuby::SubscriptionDefinitions::JAM_PLATINUM_YEARLY
|
||||
20.00
|
||||
else
|
||||
0.00
|
||||
end
|
||||
|
||||
ltv = monthly_cost * 12
|
||||
begin
|
||||
puts "Sending CAPI Subscribe event #{current_user.email}, #{monthly_cost}, #{ltv}"
|
||||
CapiTransmitter.send_event('Subscribe', current_user, { value: monthly_cost.to_s, currency: 'USD', predicted_ltv: ltv.to_s })
|
||||
rescue => e
|
||||
puts "Error sending CAPI Subscribe event #{current_user.email}, #{e.message}"
|
||||
end
|
||||
|
||||
rescue => e
|
||||
puts "Error sending CAPI Subscribe event #{current_user.email}, #{e.message}"
|
||||
end
|
||||
end
|
||||
|
||||
def get_highest_plan(subscription)
|
||||
SubscriptionDefinitions.higher_plan(subscription.plan.plan_code, get_pending_plan_code(subscription))
|
||||
end
|
||||
|
|
@ -364,6 +391,8 @@ module JamRuby
|
|||
end
|
||||
current_user.reset_playtime
|
||||
current_user.save(validate: false)
|
||||
|
||||
report_meta_capi(current_user, plan_code)
|
||||
rescue => e
|
||||
puts "Could not create subscription for user #{current_user.email}. #{e}"
|
||||
return false, subscription, account
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ module JamRuby
|
|||
EmailProfileReminder.send_reminders
|
||||
EmailSignupSurvey.send_survey
|
||||
GearSetupReminder.send_reminders
|
||||
TestGearReminder.send_reminders
|
||||
GroupSessionReminder.send_reminders
|
||||
TrialExpiresReminder.send_reminders
|
||||
ConnectionManager.new.cleanup_dangling
|
||||
|
||||
@@log.info("done")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,139 @@
|
|||
require "spec_helper"
|
||||
|
||||
describe TrialExpiresReminder do
|
||||
let(:user1) { FactoryGirl.create(:user) }
|
||||
let(:user2) { FactoryGirl.create(:user) }
|
||||
let(:user3) { FactoryGirl.create(:user) }
|
||||
|
||||
before(:each) do
|
||||
ActionMailer::Base.delivery_method = :test
|
||||
ActionMailer::Base.perform_deliveries = true
|
||||
ActionMailer::Base.deliveries = []
|
||||
|
||||
User.delete_all
|
||||
UserMailer.deliveries.clear
|
||||
Rails.application.config.trial_expires_reminders_effective_from_date = 2.weeks.ago.to_s
|
||||
end
|
||||
|
||||
after(:each) do
|
||||
ActionMailer::Base.deliveries.clear
|
||||
end
|
||||
|
||||
def no_more_emails_sent
|
||||
UserMailer.deliveries.clear
|
||||
TrialExpiresReminder.send_reminders
|
||||
expect(ActionMailer::Base.deliveries.count).to eq(0)
|
||||
end
|
||||
|
||||
it "sends reminder emails to users whose trials are about to expire" do
|
||||
user1.subscription_trial_ends_at = 1.days.from_now
|
||||
user1.subscription_sync_code = 'trial_ended'
|
||||
user1.save!
|
||||
|
||||
user2.subscription_trial_ends_at = 2.days.ago
|
||||
user2.subscription_sync_code = 'trial_ended'
|
||||
user2.save!
|
||||
|
||||
TrialExpiresReminder.send_reminders
|
||||
|
||||
expect(ActionMailer::Base.deliveries.count).to eq(1)
|
||||
expect(ActionMailer::Base.deliveries.map(&:to).flatten).to include(user2.email)
|
||||
# Check if the first reminder email was sent by verifying the subject
|
||||
expect(ActionMailer::Base.deliveries.last.subject).to include("Your free gold trial has expired, but you have great options to keep playing!")
|
||||
|
||||
expect(user2.reload.trial_expires_reminder1_sent_at).not_to be_nil
|
||||
|
||||
no_more_emails_sent
|
||||
end
|
||||
|
||||
it "does not send reminder emails to users who have already received them" do
|
||||
user1.reload
|
||||
user1.subscription_trial_ends_at = 1.days.ago + 1.hour
|
||||
user1.subscription_sync_code = 'trial_ended'
|
||||
user1.trial_expires_reminder1_sent_at = Time.now
|
||||
user1.save!
|
||||
|
||||
TrialExpiresReminder.send_reminders
|
||||
|
||||
expect(ActionMailer::Base.deliveries.count).to eq(0)
|
||||
end
|
||||
|
||||
it "sends the second reminder email to users whose trials are about to expire" do
|
||||
user1.reload
|
||||
user2.reload
|
||||
|
||||
# pretend that the first reminder email was sent 2 days ago
|
||||
user1.subscription_trial_ends_at = 4.days.ago + 1.hour
|
||||
user1.subscription_sync_code = 'trial_ended'
|
||||
user1.trial_expires_reminder1_sent_at = 5.days.ago
|
||||
user1.save!
|
||||
|
||||
user2.subscription_trial_ends_at = 4.days.ago + 1.hour
|
||||
user2.subscription_sync_code = 'trial_ended'
|
||||
user2.trial_expires_reminder1_sent_at = Time.now
|
||||
user2.save!
|
||||
|
||||
TrialExpiresReminder.send_reminders
|
||||
|
||||
expect(ActionMailer::Base.deliveries.count).to eq(1)
|
||||
expect(ActionMailer::Base.deliveries.map(&:to).flatten).to include(user1.email)
|
||||
# Check if the second reminder email was sent by verifying the subject
|
||||
expect(ActionMailer::Base.deliveries.last.subject).to include("Don’t forget to check your options to keep playing")
|
||||
|
||||
expect(user1.reload.trial_expires_reminder2_sent_at).not_to be_nil
|
||||
|
||||
no_more_emails_sent
|
||||
end
|
||||
|
||||
it "sends the third reminder email to users whose trials are about to expire" do
|
||||
user1.reload
|
||||
user1.subscription_trial_ends_at = 10.days.ago + 1.hour
|
||||
user1.subscription_sync_code = 'trial_ended'
|
||||
user1.trial_expires_reminder1_sent_at = 8.days.ago
|
||||
user1.trial_expires_reminder2_sent_at = 9.days.ago
|
||||
user1.save!
|
||||
|
||||
TrialExpiresReminder.send_reminders
|
||||
|
||||
expect(ActionMailer::Base.deliveries.count).to eq(1)
|
||||
expect(ActionMailer::Base.deliveries.map(&:to).flatten).to include(user1.email)
|
||||
# Check if the third reminder email was sent by verifying the subject
|
||||
expect(ActionMailer::Base.deliveries.last.subject).to include("One last reminder!")
|
||||
|
||||
expect(user1.reload.trial_expires_reminder3_sent_at).not_to be_nil
|
||||
|
||||
no_more_emails_sent
|
||||
end
|
||||
|
||||
it "sends first and second and third reminder emails to users whose trials are about to expire" do
|
||||
user1.reload
|
||||
user2.reload
|
||||
user3.reload
|
||||
|
||||
user1.subscription_trial_ends_at = 2.days.ago
|
||||
user1.subscription_sync_code = 'trial_ended'
|
||||
user1.save!
|
||||
|
||||
user2.subscription_trial_ends_at = 2.days.ago
|
||||
user2.trial_expires_reminder1_sent_at = 5.days.ago
|
||||
user2.subscription_sync_code = 'trial_ended'
|
||||
user2.save!
|
||||
|
||||
user3.subscription_trial_ends_at = 2.days.ago
|
||||
user3.trial_expires_reminder1_sent_at = 8.days.ago
|
||||
user3.trial_expires_reminder2_sent_at = 9.days.ago
|
||||
user3.subscription_sync_code = 'trial_ended'
|
||||
user3.save!
|
||||
|
||||
TrialExpiresReminder.send_reminders
|
||||
|
||||
expect(user1.reload.trial_expires_reminder1_sent_at).not_to be_nil
|
||||
expect(user2.reload.trial_expires_reminder2_sent_at).not_to be_nil
|
||||
expect(user3.reload.trial_expires_reminder3_sent_at).not_to be_nil
|
||||
|
||||
expect(ActionMailer::Base.deliveries.count).to eq(3)
|
||||
expect(ActionMailer::Base.deliveries.map(&:to).flatten).to include(user1.email, user2.email, user3.email)
|
||||
|
||||
no_more_emails_sent
|
||||
end
|
||||
end
|
||||
|
|
@ -16,7 +16,7 @@ describe UserMailer do
|
|||
UserMailer.deliveries.clear
|
||||
end
|
||||
|
||||
describe "should send confirm email", focus: true do
|
||||
describe "should send confirm email" do
|
||||
|
||||
let (:mail) { UserMailer.deliveries[0] }
|
||||
let (:signup_confirmation_url) { "/confirm" }
|
||||
|
|
@ -210,4 +210,227 @@ describe UserMailer do
|
|||
# it { mail.text_part.body.include?("New JamKazam Musicians in your Area").should be_true }
|
||||
# end
|
||||
|
||||
describe "sends group session reminder 1", focus: true do
|
||||
let(:mail) { ActionMailer::Base.deliveries.last }
|
||||
|
||||
before(:each) do
|
||||
ActionMailer::Base.deliveries.clear
|
||||
UserMailer.group_session_reminder1(user).deliver_now
|
||||
end
|
||||
|
||||
it "sends exactly one email" do
|
||||
expect(ActionMailer::Base.deliveries.length).to eq(1)
|
||||
end
|
||||
|
||||
it "has the correct from address" do
|
||||
#expect(mail.from).to eq([UserMailer::DEFAULT_SENDER])
|
||||
mail['from'].to_s.should == UserMailer::DEFAULT_SENDER
|
||||
end
|
||||
|
||||
it "has the correct recipient" do
|
||||
expect(mail.to).to eq([user.email])
|
||||
end
|
||||
|
||||
it "is multipart" do
|
||||
expect(mail).to be_multipart
|
||||
end
|
||||
|
||||
it "has the expected subject" do
|
||||
expect(mail.subject).to include("It’s time to start playing music with others online!")
|
||||
end
|
||||
|
||||
it "includes the correct content in the HTML part" do
|
||||
expect(mail.html_part.body.to_s).to include("Now that you have set up your gear and checked it out in a solo session on your own")
|
||||
end
|
||||
|
||||
it "includes the correct content in the text part" do
|
||||
expect(mail.text_part.body.to_s).to include("Now that you have set up your gear and checked it out in a solo session on your own")
|
||||
end
|
||||
end
|
||||
|
||||
describe "sends group session reminder 2", focus: true do
|
||||
let(:mail) { ActionMailer::Base.deliveries.last }
|
||||
|
||||
before(:each) do
|
||||
ActionMailer::Base.deliveries.clear
|
||||
UserMailer.group_session_reminder2(user).deliver_now
|
||||
end
|
||||
|
||||
it "sends exactly one email" do
|
||||
expect(ActionMailer::Base.deliveries.length).to eq(1)
|
||||
end
|
||||
|
||||
it "has the correct from address" do
|
||||
mail['from'].to_s.should == UserMailer::DEFAULT_SENDER
|
||||
end
|
||||
|
||||
it "has the correct recipient" do
|
||||
expect(mail.to).to eq([user.email])
|
||||
end
|
||||
|
||||
it "is multipart" do
|
||||
expect(mail).to be_multipart
|
||||
end
|
||||
|
||||
it "has the expected subject" do
|
||||
expect(mail.subject).to include("Take the last step to play online with others now, and experience the joy")
|
||||
end
|
||||
|
||||
it "includes the correct content in the HTML part" do
|
||||
expect(mail.html_part.body.to_s).to include("If you signed up for JamKazam to play music with others online")
|
||||
end
|
||||
|
||||
it "includes the correct content in the text part" do
|
||||
expect(mail.text_part.body.to_s).to include("If you signed up for JamKazam to play music with others online")
|
||||
end
|
||||
end
|
||||
|
||||
describe "sends group session reminder 3", focus: true do
|
||||
let(:mail) { ActionMailer::Base.deliveries.last }
|
||||
|
||||
before(:each) do
|
||||
ActionMailer::Base.deliveries.clear
|
||||
UserMailer.group_session_reminder3(user).deliver_now
|
||||
end
|
||||
|
||||
it "sends exactly one email" do
|
||||
expect(ActionMailer::Base.deliveries.length).to eq(1)
|
||||
end
|
||||
|
||||
it "has the correct from address" do
|
||||
mail['from'].to_s.should == UserMailer::DEFAULT_SENDER
|
||||
end
|
||||
|
||||
it "has the correct recipient" do
|
||||
expect(mail.to).to eq([user.email])
|
||||
end
|
||||
|
||||
it "is multipart" do
|
||||
expect(mail).to be_multipart
|
||||
end
|
||||
|
||||
it "has the expected subject" do
|
||||
expect(mail.subject).to include("Don’t waste your free 30-day premium gold plan–get in a session with others now!")
|
||||
end
|
||||
|
||||
it "includes the correct content in the HTML part" do
|
||||
expect(mail.html_part.body.to_s).to include("When you sign up for JamKazam, we give you a free 30-day premium")
|
||||
end
|
||||
|
||||
it "includes the correct content in the text part" do
|
||||
expect(mail.text_part.body.to_s).to include("When you sign up for JamKazam, we give you a free 30-day premium")
|
||||
end
|
||||
end
|
||||
|
||||
describe "sends trial expires reminder 1", focus: true do
|
||||
let(:mail) { ActionMailer::Base.deliveries.last }
|
||||
|
||||
before(:each) do
|
||||
ActionMailer::Base.deliveries.clear
|
||||
UserMailer.trial_expires_reminder1(user).deliver_now
|
||||
end
|
||||
|
||||
it "sends exactly one email" do
|
||||
expect(ActionMailer::Base.deliveries.length).to eq(1)
|
||||
end
|
||||
|
||||
it "has the correct from address" do
|
||||
mail['from'].to_s.should == UserMailer::DEFAULT_SENDER
|
||||
end
|
||||
|
||||
it "has the correct recipient" do
|
||||
expect(mail.to).to eq([user.email])
|
||||
end
|
||||
|
||||
it "is multipart" do
|
||||
expect(mail).to be_multipart
|
||||
end
|
||||
|
||||
it "has the expected subject" do
|
||||
expect(mail.subject).to include("Your free gold trial has expired, but you have great options to keep playing!")
|
||||
end
|
||||
|
||||
it "includes the correct content in the HTML part" do
|
||||
expect(mail.html_part.body.to_s).to include("We hope you’ve enjoyed your 30-day free gold trial with JamKazam")
|
||||
end
|
||||
|
||||
it "includes the correct content in the text part" do
|
||||
expect(mail.text_part.body.to_s).to include("We hope you’ve enjoyed your 30-day free gold trial with JamKazam")
|
||||
end
|
||||
end
|
||||
|
||||
describe "sends trial expires reminder 2", focus: true do
|
||||
let(:mail) { ActionMailer::Base.deliveries.last }
|
||||
|
||||
before(:each) do
|
||||
ActionMailer::Base.deliveries.clear
|
||||
UserMailer.trial_expires_reminder2(user).deliver_now
|
||||
end
|
||||
|
||||
it "sends exactly one email" do
|
||||
expect(ActionMailer::Base.deliveries.length).to eq(1)
|
||||
end
|
||||
|
||||
it "has the correct from address" do
|
||||
mail['from'].to_s.should == UserMailer::DEFAULT_SENDER
|
||||
end
|
||||
|
||||
it "has the correct recipient" do
|
||||
expect(mail.to).to eq([user.email])
|
||||
end
|
||||
|
||||
it "is multipart" do
|
||||
expect(mail).to be_multipart
|
||||
end
|
||||
|
||||
it "has the expected subject" do
|
||||
expect(mail.subject).to include("Don’t forget to check your options to keep playing")
|
||||
end
|
||||
|
||||
it "includes the correct content in the HTML part" do
|
||||
expect(mail.html_part.body.to_s).to include("Your 30-day free gold trial with JamKazam has expired")
|
||||
end
|
||||
|
||||
it "includes the correct content in the text part" do
|
||||
expect(mail.text_part.body.to_s).to include("Your 30-day free gold trial with JamKazam has expired")
|
||||
end
|
||||
end
|
||||
|
||||
describe "sends trial expires reminder 3", focus: true do
|
||||
let(:mail) { ActionMailer::Base.deliveries.last }
|
||||
|
||||
before(:each) do
|
||||
ActionMailer::Base.deliveries.clear
|
||||
UserMailer.trial_expires_reminder3(user).deliver_now
|
||||
end
|
||||
|
||||
it "sends exactly one email" do
|
||||
expect(ActionMailer::Base.deliveries.length).to eq(1)
|
||||
end
|
||||
|
||||
it "has the correct from address" do
|
||||
mail['from'].to_s.should == UserMailer::DEFAULT_SENDER
|
||||
end
|
||||
|
||||
it "has the correct recipient" do
|
||||
expect(mail.to).to eq([user.email])
|
||||
end
|
||||
|
||||
it "is multipart" do
|
||||
expect(mail).to be_multipart
|
||||
end
|
||||
|
||||
it "has the expected subject" do
|
||||
expect(mail.subject).to include("One last reminder!")
|
||||
end
|
||||
|
||||
it "includes the correct content in the HTML part" do
|
||||
expect(mail.html_part.body.to_s).to include("Your 30-day free gold trial with JamKazam has expired")
|
||||
end
|
||||
|
||||
it "includes the correct content in the text part" do
|
||||
expect(mail.text_part.body.to_s).to include("Your 30-day free gold trial with JamKazam has expired")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
# trigger 1773232627
|
||||
|
|
@ -55,6 +55,7 @@
|
|||
//= require AAB_message_factory
|
||||
//= require jam_rest
|
||||
//= require ga
|
||||
//= require meta_tracking
|
||||
//= require utils
|
||||
//= require subscription_utils
|
||||
//= require profile_utils
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
//= require AAB_message_factory
|
||||
//= require jam_rest
|
||||
//= require ga
|
||||
//= require meta_tracking
|
||||
//= require layout
|
||||
//= require jamkazam
|
||||
//= require utils
|
||||
|
|
|
|||
|
|
@ -8,5 +8,6 @@
|
|||
//= require utils
|
||||
//= require jam_rest
|
||||
//= require ga
|
||||
//= require meta_tracking
|
||||
//= require corp/init
|
||||
//= require_directory ../corp
|
||||
|
|
@ -64,6 +64,7 @@
|
|||
//= require custom_controls
|
||||
//= require jam_rest
|
||||
//= require ga
|
||||
//= require meta_tracking
|
||||
//= require session_utils
|
||||
//= require recording_utils
|
||||
//= require helpBubbleHelper
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@
|
|||
//= require ui_helper
|
||||
//= require jam_rest
|
||||
//= require ga
|
||||
//= require meta_tracking
|
||||
//= require recordingModel
|
||||
//= require web/signup_helper
|
||||
//= require web/signin_helper
|
||||
|
|
|
|||
|
|
@ -0,0 +1,117 @@
|
|||
/**
|
||||
* meta_tracking.js
|
||||
* A standalone module to capture and persist Meta attribution signals (fbclid, _fbp) in cookies.
|
||||
*
|
||||
* Logic adapted for legacy environment (no React hooks).
|
||||
* - Checks URL for `fbclid` and sets `_fbc` cookie.
|
||||
* - Checks for `_fbp` cookie; if missing, generates and sets it.
|
||||
*/
|
||||
|
||||
(function (window, document) {
|
||||
'use strict';
|
||||
|
||||
var MetaTracking = {
|
||||
init: function () {
|
||||
var location = window.location;
|
||||
this.handleFbc(location.search);
|
||||
this.handleFbp();
|
||||
this.handleUtm(location.search);
|
||||
},
|
||||
|
||||
// 1. Parsing and storing _fbc (Click ID)
|
||||
handleFbc: function (searchParams) {
|
||||
var fbclid = this.getQueryParam('fbclid', searchParams);
|
||||
|
||||
if (fbclid) {
|
||||
var version = 'fb';
|
||||
var subdomainIndex = 1; // 1 = example.com
|
||||
var creationTime = new Date().getTime(); // Unix timestamp in ms
|
||||
|
||||
// Format: fb.1.timestamp.id
|
||||
var fbcValue = version + '.' + subdomainIndex + '.' + creationTime + '.' + fbclid;
|
||||
|
||||
this.setCookie('_fbc', fbcValue, 90);
|
||||
}
|
||||
},
|
||||
|
||||
handleUtm: function (searchParams) {
|
||||
var self = this;
|
||||
if (!searchParams) return;
|
||||
|
||||
// Logically, we want to capture all utm_ parameters.
|
||||
// We can either iterate a list or dynamic regex.
|
||||
// Given the requirement to be robust, let's look for "utm_"
|
||||
|
||||
var query = searchParams.substring(1); // remove '?'
|
||||
var vars = query.split('&');
|
||||
for (var i = 0; i < vars.length; i++) {
|
||||
var pair = vars[i].split('=');
|
||||
if (pair.length === 2) {
|
||||
var key = decodeURIComponent(pair[0]);
|
||||
var value = decodeURIComponent(pair[1]);
|
||||
if (key.indexOf('utm_') === 0) {
|
||||
self.setCookie(key, value, 90);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 2. Handling _fbp (Browser ID)
|
||||
handleFbp: function () {
|
||||
if (!this.getCookie('_fbp')) {
|
||||
var version = 'fb';
|
||||
var subdomainIndex = 1;
|
||||
var creationTime = new Date().getTime();
|
||||
var randomInt = Math.floor(Math.random() * 10000000000); // 10-digit random number
|
||||
|
||||
// Format: fb.1.timestamp.randomDigits
|
||||
var fbpValue = version + '.' + subdomainIndex + '.' + creationTime + '.' + randomInt;
|
||||
|
||||
this.setCookie('_fbp', fbpValue, 90);
|
||||
}
|
||||
},
|
||||
|
||||
// Helper: Get query param by name
|
||||
getQueryParam: function (name, search) {
|
||||
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
|
||||
var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
|
||||
var results = regex.exec(search);
|
||||
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
|
||||
},
|
||||
|
||||
// Helper: Set cookie
|
||||
setCookie: function (name, value, days) {
|
||||
var expires = "";
|
||||
if (days) {
|
||||
var date = new Date();
|
||||
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||
expires = "; expires=" + date.toUTCString();
|
||||
}
|
||||
// Ensure path is root and domain is included if needed (defaults to current host)
|
||||
document.cookie = name + "=" + (value || "") + expires + "; path=/";
|
||||
},
|
||||
|
||||
// Helper: Get cookie
|
||||
getCookie: function (name) {
|
||||
var nameEQ = name + "=";
|
||||
var ca = document.cookie.split(';');
|
||||
for (var i = 0; i < ca.length; i++) {
|
||||
var c = ca[i];
|
||||
while (c.charAt(0) == ' ') c = c.substring(1, c.length);
|
||||
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize on ready
|
||||
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
||||
MetaTracking.init();
|
||||
} else {
|
||||
// IE9+ support for DOMContentLoaded
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
MetaTracking.init();
|
||||
});
|
||||
}
|
||||
|
||||
})(window, document);
|
||||
|
|
@ -23,6 +23,7 @@
|
|||
//= require AAB_message_factory
|
||||
//= require jam_rest
|
||||
//= require ga
|
||||
//= require meta_tracking
|
||||
//= require layout
|
||||
//= require jamkazam
|
||||
//= require utils
|
||||
|
|
@ -37,11 +38,11 @@
|
|||
|
||||
function initializeInfluxDB() {
|
||||
window.stats = new InfluxDB({
|
||||
"host" : gon.global.influxdb_host,
|
||||
"port" : gon.global.influxdb_port,
|
||||
"username" : gon.global.influxdb_username,
|
||||
"password" : gon.global.influxdb_password,
|
||||
"database" : gon.global.influxdb_database
|
||||
"host": gon.global.influxdb_host,
|
||||
"port": gon.global.influxdb_port,
|
||||
"username": gon.global.influxdb_username,
|
||||
"password": gon.global.influxdb_password,
|
||||
"database": gon.global.influxdb_database
|
||||
});
|
||||
|
||||
window.stats.write = window.stats.writePoint;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
//= require utils
|
||||
//= require subscription_utils
|
||||
//= require jamkazam
|
||||
//= require modern/JamServer_copy
|
||||
//= require JamServer
|
||||
|
||||
//= require fakeJamClient
|
||||
//= require fakeJamClientMessages
|
||||
|
|
|
|||
|
|
@ -619,7 +619,7 @@ profileUtils = context.JK.ProfileUtils
|
|||
<a className={classNames(submitClassNames)} onClick={this.onSubmitForm}>SUBMIT CARD INFORMATION</a>
|
||||
</div>`
|
||||
else
|
||||
header = 'You have have a payment method on file already.'
|
||||
header = 'You have a payment method on file already.'
|
||||
updateCardAction = `<a className={classNames(updateCardClassNames)} onClick={this.onUnlockPaymentInfo}>I'D LIKE TO UPDATE MY PAYMENT INFO</a>`
|
||||
managedSubscriptionAction = `<a href="/client#/account/subscription" className="button-orange">MANAGE MY SUBSCRIPTION</a>`
|
||||
actions = `<div className="actions">
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@
|
|||
//= require custom_controls
|
||||
//= require jam_rest
|
||||
//= require ga
|
||||
//= require meta_tracking
|
||||
//= require session_utils
|
||||
//= require recording_utils
|
||||
//= require helpBubbleHelper
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
class ApiJamTracksController < ApiController
|
||||
|
||||
# have to be signed in currently to see this screen
|
||||
before_filter :api_signed_in_user, :except => [:index, :autocomplete, :show_with_artist_info, :artist_index]
|
||||
before_filter :api_signed_in_user, :except => [:index, :autocomplete, :show_with_artist_info, :artist_index, :public_show]
|
||||
before_filter :api_any_user, :only => [:index, :autocomplete, :show_with_artist_info, :artist_index]
|
||||
before_filter :lookup_jam_track_right, :only => [:download, :enqueue, :show_jam_track_right, :mark_active, :download_stem]
|
||||
before_filter :ip_blacklist, :only => [:download_stem, :download]
|
||||
|
|
@ -23,6 +23,11 @@ class ApiJamTracksController < ApiController
|
|||
@jam_track = JamTrack.find_by_plan_code!(params[:plan_code])
|
||||
end
|
||||
|
||||
def public_show
|
||||
@jam_track = JamTrack.find_by!(slug: params[:slug])
|
||||
render "api_jam_tracks/show_for_client", :layout => nil
|
||||
end
|
||||
|
||||
def index
|
||||
data = JamTrack.index(params, any_user)
|
||||
@jam_tracks, @next, @count = data[0], data[1], data[2]
|
||||
|
|
|
|||
|
|
@ -820,10 +820,6 @@ class ApiMusicSessionsController < ApiController
|
|||
return render json: { code: "invalid_token", message: "No token found for '#{params[:token]}'" }, status: :forbidden
|
||||
end
|
||||
|
||||
if token.expired_at < Time.now.utc
|
||||
return render json: {code: "expired_token", message: "The token has expired" }, status: :forbidden
|
||||
end
|
||||
|
||||
if token.token == Rails.application.config.video_open_room
|
||||
return render json: { name: token.user.name, user_id: token.user.id }, status: 200
|
||||
end
|
||||
|
|
|
|||
|
|
@ -41,7 +41,9 @@ class ApiRecurlyController < ApiController
|
|||
reuse_card: reuse_card_next_time,
|
||||
affiliate_referral_id: cookies[:affiliate_visitor],
|
||||
origin: origin_cookie,
|
||||
timezone: current_timezone
|
||||
timezone: current_timezone,
|
||||
facebook_click_id: cookies[:_fbc],
|
||||
facebook_browser_id: cookies[:_fbp]
|
||||
}
|
||||
|
||||
options = User.musician_defaults(request.remote_ip, ApplicationHelper.base_uri(request) + "/confirm", any_user, options)
|
||||
|
|
|
|||
|
|
@ -158,7 +158,9 @@ class ApiUsersController < ApiController
|
|||
affiliate_referral_id: cookies[:affiliate_visitor],
|
||||
origin: origin_cookie,
|
||||
test_drive_package: params[:test_drive_package],
|
||||
timezone: current_timezone
|
||||
timezone: current_timezone,
|
||||
facebook_click_id: cookies[:_fbc],
|
||||
facebook_browser_id: cookies[:_fbp]
|
||||
}
|
||||
|
||||
options = User.musician_defaults(request.remote_ip, ApplicationHelper.base_uri(request) + "/confirm", any_user, options)
|
||||
|
|
@ -615,6 +617,15 @@ class ApiUsersController < ApiController
|
|||
# NOTE: if you change confirm_email_link value below, you break outstanding email changes because links in user inboxes are broken
|
||||
confirm_email_link = confirm_email_url + "?token="
|
||||
|
||||
do_bigin_complete_email(confirm_email_link)
|
||||
end
|
||||
|
||||
def begin_update_email_alt
|
||||
confirm_email_link = ApplicationHelper.spa_base_uri + '/confirm-email-change' + "?token="
|
||||
do_bigin_complete_email(confirm_email_link)
|
||||
end
|
||||
|
||||
def do_bigin_complete_email(confirm_email_link)
|
||||
current_user.begin_update_email(params[:update_email], params[:current_password], confirm_email_link)
|
||||
|
||||
if current_user.errors.any?
|
||||
|
|
|
|||
|
|
@ -49,11 +49,22 @@ class ApplicationController < ActionController::Base
|
|||
|
||||
def origin_cookie
|
||||
begin
|
||||
JSON.parse(cookies[:origin]) if cookies[:origin]
|
||||
data = JSON.parse(cookies[:origin]) if cookies[:origin]
|
||||
rescue
|
||||
nil
|
||||
data = nil
|
||||
end
|
||||
|
||||
# Backfill with individual UTM cookies if present
|
||||
# This supports cases where the frontend (jam-ui/web) set specific cookies
|
||||
# or if the JSON cookie is missing/incomplete.
|
||||
%w(utm_source utm_medium utm_campaign utm_term utm_content utm_id).each do |key|
|
||||
if cookies[key].present?
|
||||
data ||= {}
|
||||
data[key] = cookies[key]
|
||||
end
|
||||
end
|
||||
|
||||
data
|
||||
end
|
||||
|
||||
def track_origin
|
||||
|
|
|
|||
|
|
@ -635,6 +635,10 @@ class LandingsController < ApplicationController
|
|||
musician: true,
|
||||
timezone: current_timezone,
|
||||
first_name: @first,
|
||||
origin: origin_cookie,
|
||||
affiliate_referral_id: cookies[:affiliate_visitor],
|
||||
facebook_click_id: cookies[:_fbc],
|
||||
facebook_browser_id: cookies[:_fbp],
|
||||
instruments: [{:instrument_id => @instrument, :proficiency_level => 1, :priority => 1}])
|
||||
if @user.errors.any?
|
||||
first = @user.errors.first
|
||||
|
|
|
|||
|
|
@ -120,7 +120,10 @@ class SessionsController < ApplicationController
|
|||
last_name: auth_hash[:info][:last_name],
|
||||
email: auth_hash[:info][:email],
|
||||
timezone: current_timezone,
|
||||
affiliate_referral_id: cookies[:affiliate_visitor])
|
||||
affiliate_referral_id: cookies[:affiliate_visitor],
|
||||
origin: origin_cookie,
|
||||
facebook_click_id: cookies[:_fbc],
|
||||
facebook_browser_id: cookies[:_fbp])
|
||||
|
||||
# Users who sign up using oauth are presumed to have valid email adddresses.
|
||||
user.confirm_email!
|
||||
|
|
@ -196,7 +199,9 @@ class SessionsController < ApplicationController
|
|||
location: {:country => nil, :state => nil, :city => nil},
|
||||
affiliate_referral_id: cookies[:affiliate_visitor],
|
||||
origin: origin_cookie,
|
||||
timezone: current_timezone
|
||||
timezone: current_timezone,
|
||||
facebook_click_id: cookies[:_fbc],
|
||||
facebook_browser_id: cookies[:_fbp]
|
||||
}
|
||||
|
||||
options = User.musician_defaults(request.remote_ip, ApplicationHelper.base_uri(request) + "/confirm", any_user, options)
|
||||
|
|
|
|||
|
|
@ -192,7 +192,11 @@ class UsersController < ApplicationController
|
|||
affiliate_partner: @affiliate_partner,
|
||||
timezone: current_timezone,
|
||||
origin: origin_cookie,
|
||||
desired_plan_code: desired_plan_code )
|
||||
timezone: current_timezone,
|
||||
origin: origin_cookie,
|
||||
desired_plan_code: desired_plan_code,
|
||||
facebook_click_id: cookies[:_fbc],
|
||||
facebook_browser_id: cookies[:_fbp])
|
||||
|
||||
rend = _render('new')
|
||||
# check for errors
|
||||
|
|
@ -439,18 +443,21 @@ JS
|
|||
end
|
||||
|
||||
def unsubscribe
|
||||
unless @user = User.read_access_token(params[:user_token])
|
||||
redirect_to '/'
|
||||
end if params[:user_token].present?
|
||||
unless params[:user_token].present? && (@user = User.read_access_token(params[:user_token]))
|
||||
respond_to do |format|
|
||||
format.html { redirect_to '/', alert: 'Invalid or expired token.' }
|
||||
format.json { render json: { status: 'error', message: 'Invalid or expired token.' }, status: :unprocessable_entity }
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
#if request.get?
|
||||
@user.subscribe_email = false
|
||||
@user.save!
|
||||
|
||||
#elsif request.post?
|
||||
@user.subscribe_email = false
|
||||
@user.save!
|
||||
#end
|
||||
|
||||
render text: 'You have been unsubscribed.'
|
||||
respond_to do |format|
|
||||
format.html { render plain: 'You have been unsubscribed.' }
|
||||
format.json { render json: { status: 'success', message: 'You have been unsubscribed.' } }
|
||||
end
|
||||
end
|
||||
|
||||
def unsubscribe_user_match
|
||||
|
|
|
|||
|
|
@ -10,10 +10,6 @@ node :jmep do |jam_track|
|
|||
jam_track.jmep_json ? jam_track.jmep_json : nil
|
||||
end
|
||||
|
||||
node :can_download do |item|
|
||||
!!item.right_for_user(current_user, ShoppingCart::JAMTRACK_FULL)
|
||||
end
|
||||
|
||||
child(:jam_track_tracks => :tracks) {
|
||||
|
||||
attributes :id, :part, :instrument, :track_type, :position
|
||||
|
|
@ -27,22 +23,28 @@ child(:jam_track_tracks => :tracks) {
|
|||
end
|
||||
}
|
||||
|
||||
node do |jam_track|
|
||||
right = jam_track.right_for_user(current_user)
|
||||
if right
|
||||
{
|
||||
jam_track_right_id: right.id,
|
||||
purchased_at: right.created_at.to_i,
|
||||
last_mixdown_id: right.last_mixdown_id,
|
||||
last_stem_id: right.last_stem_id
|
||||
}
|
||||
if current_user
|
||||
node do |jam_track|
|
||||
right = jam_track.right_for_user(current_user)
|
||||
if right
|
||||
{
|
||||
jam_track_right_id: right.id,
|
||||
purchased_at: right.created_at.to_i,
|
||||
last_mixdown_id: right.last_mixdown_id,
|
||||
last_stem_id: right.last_stem_id
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
node :mixdowns do |jam_track|
|
||||
items = []
|
||||
jam_track.mixdowns_for_user(current_user).each do |mixdown|
|
||||
items << partial("api_jam_track_mixdowns/show", :object => mixdown)
|
||||
node :mixdowns do |jam_track|
|
||||
items = []
|
||||
jam_track.mixdowns_for_user(current_user).each do |mixdown|
|
||||
items << partial("api_jam_track_mixdowns/show", :object => mixdown)
|
||||
end
|
||||
items
|
||||
end
|
||||
|
||||
node :can_download do |item|
|
||||
!!item.right_for_user(current_user, ShoppingCart::JAMTRACK_FULL)
|
||||
end
|
||||
items
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
object @profile
|
||||
|
||||
attributes :id, :first_name, :last_name, :name, :city, :state, :country, :location, :online, :photo_url, :musician, :gender, :birth_date, :internet_service_provider, :friend_count, :liker_count, :like_count, :follower_count, :following_count, :recording_count, :session_count, :biography, :favorite_count, :audio_latency, :upcoming_session_count, :age, :website, :skill_level, :concert_count, :studio_session_count, :virtual_band, :virtual_band_commitment, :traditional_band, :traditional_band_commitment, :traditional_band_touring, :paid_sessions, :paid_sessions_hourly_rate,
|
||||
:paid_sessions_daily_rate, :free_sessions, :cowriting, :cowriting_purpose, :subscribe_email, :is_a_teacher, :is_a_student, :last_active_timestamp, :v2_photo_url, :v2_photo_uploaded
|
||||
:paid_sessions_daily_rate, :free_sessions, :cowriting, :cowriting_purpose, :subscribe_email, :is_a_teacher, :is_a_student, :last_active_timestamp, :v2_photo_url, :v2_photo_uploaded, :has_stored_credit_card?
|
||||
|
||||
node :has_recurly_account do |user|
|
||||
user.recurly_code == user.id
|
||||
end
|
||||
|
||||
child :online_presences => :online_presences do
|
||||
attributes :id, :service_type, :username
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue