Compare commits
No commits in common. "develop" and "paypal_checkout_flow" have entirely different histories.
develop
...
paypal_che
|
|
@ -1,25 +0,0 @@
|
|||
name: Build Admin
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: dagger
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: https://github.com/actions/checkout@v4
|
||||
|
||||
- name: Install Dagger
|
||||
run: |
|
||||
curl -L https://dl.dagger.io/dagger/install.sh | sh
|
||||
sudo mv bin/dagger /usr/local/bin/
|
||||
|
||||
- name: Login to Gitea Registry
|
||||
run: echo "${{ gitea.token }}" | docker login git.staging.jamkazam.com -u ${{ gitea.actor }} --password-stdin
|
||||
|
||||
- name: Build and Publish with Dagger
|
||||
working-directory: ./admin
|
||||
run: |
|
||||
dagger call build-local --source=. --repo-root=../ publish --address=git.staging.jamkazam.com/seth/jam-cloud-admin:latest
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
name: Test Runner
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo "Runner is working!"
|
||||
|
|
@ -2,7 +2,7 @@ ActiveAdmin.register JamRuby::AffiliateQuarterlyPayment, :as => 'Affiliate Quart
|
|||
|
||||
menu :label => 'Quarterly Reports', :parent => 'Affiliates'
|
||||
|
||||
config.sort_order = 'year desc, quarter desc, due_amount_in_cents desc'
|
||||
config.sort_order = 'due_amount_in_cents DESC'
|
||||
config.batch_actions = false
|
||||
config.clear_action_items!
|
||||
config.filters = true
|
||||
|
|
@ -14,32 +14,18 @@ ActiveAdmin.register JamRuby::AffiliateQuarterlyPayment, :as => 'Affiliate Quart
|
|||
filter :quarter
|
||||
filter :closed
|
||||
filter :paid
|
||||
filter :jamtracks_sold
|
||||
filter :subscriptions_count
|
||||
filter :due_amount_in_cents
|
||||
|
||||
form :partial => 'form'
|
||||
|
||||
scope("Sorted By Due Amount", default: true) { |scope| scope.order('year desc, quarter desc, due_amount_in_cents desc') }
|
||||
scope("Sorted By Jamtracks Sold", default: false) { |scope| scope.order('year desc, quarter desc, jamtracks_sold desc') }
|
||||
scope("Sorted By Subs", default: false) { |scope| scope.order('year desc, quarter desc, subscriptions_count desc') }
|
||||
scope("Sorted By Newest First") { |scope| scope.order('year desc, quarter desc, id desc') }
|
||||
scope("Any") { |scope| scope.order('year desc, quarter desc, due_amount_in_cents desc') }
|
||||
|
||||
|
||||
index do
|
||||
|
||||
# default_actions # use this for all view/edit/delete links
|
||||
|
||||
column 'Year' do |oo| oo.year end
|
||||
column 'Quarter' do |oo| oo.quarter end
|
||||
column 'Partner Id' do |oo| oo.affiliate_partner.id end
|
||||
column 'Partner' do |oo| link_to(oo.affiliate_partner.display_name, oo.affiliate_partner.admin_url, {:title => oo.affiliate_partner.display_name}) end
|
||||
column "Tot ($)" do |oo| sprintf("$%.2f", oo.due_amount_in_cents.to_f / 100.to_f) end
|
||||
column "Sub ($)" do |oo| sprintf("$%.2f", oo.subscription_due_amount_in_cents.to_f / 100.to_f) end
|
||||
column "Jam ($)" do |oo| sprintf("$%.2f", oo.jamtrack_due_amount_in_cents.to_f / 100.to_f) end
|
||||
column 'JamTracks' do |oo| oo.jamtracks_sold end
|
||||
column 'Subscriptions' do |oo| oo.subscriptions_count end
|
||||
column "Due (\u00A2)" do |oo| oo.due_amount_in_cents end
|
||||
column 'JamTracks Sold' do |oo| oo.jamtracks_sold end
|
||||
column 'Paid' do |oo| oo.paid end
|
||||
column 'Closed' do |oo| oo.paid end
|
||||
|
||||
|
|
|
|||
|
|
@ -23,9 +23,7 @@ ActiveAdmin.register JamRuby::AffiliateTrafficTotal, :as => 'Affiliate Daily Sta
|
|||
# default_actions # use this for all view/edit/delete links
|
||||
|
||||
column 'Day' do |oo| oo.day end
|
||||
column 'Partner ID' do |oo| oo.affiliate_partner.id end
|
||||
column 'Partner Name' do |oo| oo.affiliate_partner.display_name end
|
||||
column 'Partner User' do |oo| link_to(oo.affiliate_partner.partner_user.name, admin_user_path(oo.affiliate_partner.partner_user.id), { :title => oo.affiliate_partner.partner_user.name }) end
|
||||
column 'Partner' do |oo| link_to(oo.affiliate_partner.display_name, oo.affiliate_partner.admin_url, {:title => oo.affiliate_partner.display_name}) end
|
||||
column 'Signups' do |oo| oo.signups end
|
||||
column 'Visits' do |oo| oo.visits end
|
||||
|
||||
|
|
@ -33,16 +31,6 @@ ActiveAdmin.register JamRuby::AffiliateTrafficTotal, :as => 'Affiliate Daily Sta
|
|||
|
||||
|
||||
controller do
|
||||
def scoped_collection
|
||||
rel = end_of_association_chain
|
||||
.includes([:affiliate_partner])
|
||||
.order('day DESC')
|
||||
if (ref_id = params[AffiliatePartner::PARAM_REFERRAL]).present?
|
||||
qq = ['affiliate_partner_id = ?', ref_id]
|
||||
else
|
||||
qq = ['affiliate_partner_id IS NOT NULL']
|
||||
end
|
||||
@users ||= rel.where(qq)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,32 +8,27 @@ ActiveAdmin.register JamRuby::User, :as => 'Referrals' do
|
|||
config.filters = true
|
||||
|
||||
filter :affiliate_referral
|
||||
filter :email
|
||||
|
||||
## scope("Has Signups", default: true) { |scope| scope.where('visits != 0 or signups != 0').order('day desc') }
|
||||
|
||||
index do
|
||||
column 'User' do |oo| link_to(oo.name, oo.admin_url, {:title => oo.name}) end
|
||||
column 'User Email' do |oo| oo.email end
|
||||
column 'Email' do |oo| oo.email end
|
||||
column 'Created' do |oo| oo.created_at end
|
||||
column 'Partner ID' do |oo| oo.affiliate_referral.id end
|
||||
column 'Partner Name' do |oo| oo.affiliate_referral.display_name end
|
||||
column 'Partner User' do |oo| link_to(oo.affiliate_referral.partner_user.name, admin_user_path(oo.affiliate_referral.partner_user.id), { :title => oo.affiliate_referral.partner_user.name }) end
|
||||
column 'Partner' do |oo| oo.affiliate_referral.display_name end
|
||||
end
|
||||
|
||||
controller do
|
||||
|
||||
def scoped_collection
|
||||
rel = end_of_association_chain
|
||||
.includes([:affiliate_referral])
|
||||
.order('created_at DESC')
|
||||
if (ref_id = params[AffiliatePartner::PARAM_REFERRAL]).present?
|
||||
qq = ['affiliate_referral_id = ?', ref_id]
|
||||
else
|
||||
qq = ['affiliate_referral_id IS NOT NULL']
|
||||
def scoped_collection
|
||||
rel = end_of_association_chain
|
||||
.includes([:affiliate_referral])
|
||||
.order('created_at DESC')
|
||||
if (ref_id = params[AffiliatePartner::PARAM_REFERRAL]).present?
|
||||
qq = ['affiliate_referral_id = ?', ref_id]
|
||||
else
|
||||
qq = ['affiliate_referral_id IS NOT NULL']
|
||||
end
|
||||
@users ||= rel.where(qq)
|
||||
end
|
||||
@users ||= rel.where(qq)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,35 +5,19 @@ ActiveAdmin.register JamRuby::AffiliatePartner, :as => 'Affiliates' do
|
|||
config.sort_order = 'referral_user_count DESC'
|
||||
config.batch_actions = false
|
||||
# config.clear_action_items!
|
||||
config.filters = true
|
||||
config.per_page = 100
|
||||
config.filters = false
|
||||
config.per_page = 50
|
||||
config.paginate = true
|
||||
|
||||
#form :partial => 'form'
|
||||
|
||||
|
||||
#filter :partner_user
|
||||
filter :partner_name
|
||||
filter :id
|
||||
filter :current_quarter_in_cents
|
||||
filter :cumulative_earnings_in_cents
|
||||
filter :jamtracks_sold
|
||||
filter :subscriptions_count
|
||||
filter :referral_user_count
|
||||
|
||||
scope("Sorted By Current Quarter", default: true) { |scope| scope.where('partner_user_id IS NOT NULL').order('current_quarter_in_cents desc') }
|
||||
scope("Sorted By Jamtracks Sold", default: false) { |scope| scope.where('partner_user_id IS NOT NULL').order('jamtracks_sold desc') }
|
||||
scope("Sorted By Subs", default: false) { |scope| scope.where('partner_user_id IS NOT NULL').order('subscriptions_count desc') }
|
||||
scope("Sorted By Signups", default: false) { |scope| scope.where('partner_user_id IS NOT NULL').order('referral_user_count desc') }
|
||||
scope("Sorted By Newest First") { |scope| scope.where('partner_user_id IS NOT NULL').order('id desc') }
|
||||
scope("Any") { |scope| scope.where('partner_user_id IS NOT NULL').order('referral_user_count desc') }
|
||||
scope("Active", default: true) { |scope| scope.where('partner_user_id IS NOT NULL').order('referral_user_count desc') }
|
||||
scope("Unpaid") { |partner| partner.unpaid }
|
||||
|
||||
controller do
|
||||
helper 'active_admin/subscription'
|
||||
end
|
||||
|
||||
|
||||
form do |f|
|
||||
f.inputs 'Fields' do
|
||||
f.input(:partner_name, :input_html => { :maxlength => 128 })
|
||||
|
|
@ -61,47 +45,14 @@ ActiveAdmin.register JamRuby::AffiliatePartner, :as => 'Affiliates' do
|
|||
column 'Code' do |oo|
|
||||
oo.id
|
||||
end
|
||||
column 'Signups' do |oo|
|
||||
column 'Referral Count' do |oo|
|
||||
oo.referral_user_count
|
||||
end
|
||||
column 'JamTracks' do |oo|
|
||||
oo.jamtracks_sold
|
||||
end
|
||||
column 'Subs' do |oo|
|
||||
oo.subscriptions_count
|
||||
end
|
||||
column 'Cum Earnings' do |oo|
|
||||
div do
|
||||
sprintf("Tot $%.2f", oo.cumulative_earnings_in_dollars)
|
||||
end
|
||||
div do
|
||||
sprintf("Jam $%.2f", oo.jamtrack_cumulative_earnings_in_dollars)
|
||||
end
|
||||
div do
|
||||
sprintf("Sub $%.2f", oo.subscriptions_cumulative_earnings_in_dollars)
|
||||
end
|
||||
end
|
||||
column 'Current Quarter' do |oo|
|
||||
div do
|
||||
sprintf("Tot $%.2f", oo.current_quarter_in_dollars)
|
||||
end
|
||||
div do
|
||||
sprintf("Jam $%.2f", oo.jamtrack_current_quarter_in_dollars)
|
||||
end
|
||||
div do
|
||||
sprintf("Sub $%.2f", oo.subscriptions_current_quarter_in_dollars)
|
||||
end
|
||||
column 'Earnings' do |oo|
|
||||
sprintf("$%.2f", oo.cumulative_earnings_in_dollars)
|
||||
end
|
||||
column 'Amount Owed' do |oo|
|
||||
div do
|
||||
sprintf("Tot $%.2f", oo.due_amount_in_cents.to_f / 100.to_f)
|
||||
end
|
||||
div do
|
||||
sprintf("Jam $%.2f", oo.jamtrack_due_amount_in_cents.to_f / 100.to_f)
|
||||
end
|
||||
div do
|
||||
sprintf("Sub $%.2f", oo.subscription_due_amount_in_cents.to_f / 100.to_f)
|
||||
end
|
||||
sprintf("$%.2f", oo.due_amount_in_cents.to_f / 100.to_f)
|
||||
end
|
||||
column 'Pay Actions' do |oo|
|
||||
link_to('Mark Paid', mark_paid_admin_affiliate_path(oo.id), :confirm => "Mark this affiliate as PAID?") if oo.unpaid
|
||||
|
|
@ -120,15 +71,6 @@ ActiveAdmin.register JamRuby::AffiliatePartner, :as => 'Affiliates' do
|
|||
row :address
|
||||
row :tax_identifier
|
||||
row :paypal_id
|
||||
row :venmo_user_id
|
||||
row :jamtracks_sold
|
||||
row :subscriptions_count
|
||||
row :cumulative_earnings_in_dollars
|
||||
row :jamtrack_cumulative_earnings_in_dollars
|
||||
row :subscriptions_cumulative_earnings_in_dollars
|
||||
row :current_quarter_in_dollars
|
||||
row :jamtrack_current_quarter_in_dollars
|
||||
row :subscriptions_current_quarter_in_dollars
|
||||
end
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -640,12 +640,7 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do
|
|||
autocomplete :user, :email, :full => true, :display_value => :autocomplete_display_name, extra_data: [:last_jam_addr]
|
||||
|
||||
def get_autocomplete_items(parameters)
|
||||
term = parameters[:term]
|
||||
if term.include?('@')
|
||||
User.select("email, first_name, last_name, id, last_jam_addr").where(["email = ?", term]).limit(5)
|
||||
else
|
||||
User.select("email, first_name, last_name, id, last_jam_addr").where(["email ILIKE ? OR first_name ILIKE ? OR last_name ILIKE ?", "%#{term}%", "%#{term}%", "%#{term}%"]).limit(40)
|
||||
end
|
||||
User.select("email, first_name, last_name, id, last_jam_addr").where(["email ILIKE ? OR first_name ILIKE ? OR last_name ILIKE ?", "%#{parameters[:term]}%", "%#{parameters[:term]}%", "%#{parameters[:term]}%"])
|
||||
end
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -17,14 +17,6 @@ ActiveAdmin.register JamRuby::JamTrackRight, :as => 'JamTrackRights' do
|
|||
|
||||
filter :jam_track
|
||||
|
||||
controller do
|
||||
def create
|
||||
jt_params = params[:jam_ruby_jam_track_right]
|
||||
jt_params[:jam_track] =JamRuby::JamTrack.where("id=?", jt_params[:jam_track_id_val]).first # jt_params[:jam_track_id_val]
|
||||
jt_params[:user] = JamRuby::User.where("id=?", jt_params[:user_id_val]).first # jt_params[:user_id_val]
|
||||
create!
|
||||
end
|
||||
end
|
||||
index do
|
||||
actions
|
||||
|
||||
|
|
@ -55,14 +47,9 @@ ActiveAdmin.register JamRuby::JamTrackRight, :as => 'JamTrackRights' do
|
|||
|
||||
form do |f|
|
||||
f.inputs 'New Jam Track Right' do
|
||||
#f.input :jam_track, :required=>true, collection: JamTrack.all, include_blank: false
|
||||
f.input :jam_track_id_val, :required=>true, :as => :hidden
|
||||
|
||||
f.input :jam_track, :required=>true, :as => :autocomplete, :url => autocomplete_jam_track_name_admin_jam_tracks_path, hint: 'Select a jamtrack to give to this user'
|
||||
#f.input :user, :required=>true, collection: User.all, include_blank: false
|
||||
f.input :user_id_val, :required=>true, :as => :hidden
|
||||
f.input :user, :required=>true, :as => :autocomplete, :url => autocomplete_user_email_admin_users_path, hint: 'Give a free jamtrack to this user'
|
||||
f.input :can_download, :required => true, as: :boolean, :input_html => { :checked => 'checked' }
|
||||
f.input :jam_track, :required=>true, collection: JamTrack.all, include_blank: false
|
||||
f.input :user, :required=>true, collection: User.all, include_blank: false
|
||||
f.input :can_download, :required => true, as: :boolean
|
||||
end
|
||||
f.actions
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
ActiveAdmin.register JamRuby::JamTrack, :as => 'JamTracks' do
|
||||
|
||||
collection_action :autocomplete_jam_track_name, :method => :get
|
||||
|
||||
menu :label => 'JamTracks', :parent => 'JamTracks'
|
||||
|
||||
config.sort_order = 'name_asc'
|
||||
|
|
@ -21,19 +19,6 @@ ActiveAdmin.register JamRuby::JamTrack, :as => 'JamTracks' do
|
|||
|
||||
form :partial => 'form'
|
||||
|
||||
|
||||
controller do
|
||||
|
||||
# this actually searches on first name, last name, and email, because of get_autocomplete_items defined below
|
||||
autocomplete :jam_track, :name, :full => true, :display_value => :autocomplete_display_name
|
||||
|
||||
def get_autocomplete_items(parameters)
|
||||
JamTrack.select("name, original_artist, id").where(["name ILIKE ? OR original_artist ILIKE ?", "%#{parameters[:term]}%", "%#{parameters[:term]}%"])
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
index do
|
||||
|
||||
# actions # use this for all view/edit/delete links
|
||||
|
|
|
|||
|
|
@ -1,185 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
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 "JamTrack Subscription Cohorts" do
|
||||
menu :parent => 'Reports'
|
||||
|
||||
content :title => "JamTrack Subscription Cohorts" do
|
||||
h2 "Users Grouped By Month as Paying Subscribers"
|
||||
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,
|
||||
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{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)) }
|
||||
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
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
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
|
||||
|
||||
ActiveAdmin.register_page "Subscription Cohorts" do
|
||||
menu :parent => 'Reports'
|
||||
|
||||
|
||||
content :title => "Subscription Cohorts" do
|
||||
h2 "Users Grouped By Month as Paying Subscribers"
|
||||
table_for User.select(%Q{date_trunc('month', 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
|
||||
}).group("date_trunc('month', created_at)").order("date_trunc('month', 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)) }
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -6,18 +6,15 @@ ActiveAdmin.register JamRuby::User, :as => 'UserSource' do
|
|||
config.batch_actions = false
|
||||
config.clear_action_items!
|
||||
config.filters = false
|
||||
config.per_page = 250
|
||||
|
||||
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') }
|
||||
scope("Most Recent First", default: true) { |scope| scope.unscoped.order('created_at desc')}
|
||||
|
||||
index do
|
||||
column "Email" do |user|
|
||||
user.email
|
||||
end
|
||||
column "Signup (CST)" do |user|
|
||||
user.created_at.in_time_zone("Central Time (US & Canada)")
|
||||
column "Bought TestDrive" do |user|
|
||||
!user.most_recent_test_drive_purchase.nil? ? "Yes" : "No"
|
||||
end
|
||||
column "UTM Source" do |user|
|
||||
user.origin_utm_source
|
||||
|
|
@ -28,23 +25,8 @@ 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
|
||||
|
|
|
|||
|
|
@ -7,38 +7,6 @@ function intToIP(int) {
|
|||
return part4 + "." + part3 + "." + part2 + "." + part1;
|
||||
}
|
||||
|
||||
function handleJamTrackRightsForm() {
|
||||
var $jamTrackRights = $('form#new_jam_ruby_jam_track_right');
|
||||
var $jamTrack = $jamTrackRights.find('#jam_ruby_jam_track_right_jam_track_id');
|
||||
var $jamTrackVal = $jamTrackRights.find('#jam_ruby_jam_track_right_jam_track_id_val')
|
||||
|
||||
var $user = $jamTrackRights.find('#jam_ruby_jam_track_right_user_id');
|
||||
var $userVal = $jamTrackRights.find('#jam_ruby_jam_track_right_user_id_val');
|
||||
$jamTrack.on('change', function(){
|
||||
console.log("change jam track");
|
||||
});
|
||||
|
||||
/**
|
||||
$user.on('change', function(){
|
||||
console.log("change user");
|
||||
});
|
||||
|
||||
$jamTrack.on('focus', function(){
|
||||
$userVal.val('')
|
||||
});*/
|
||||
|
||||
$jamTrack.bind('railsAutocomplete.select', function(event, data){
|
||||
$jamTrackVal.val('');
|
||||
$jamTrackVal.val(data.item.id);
|
||||
console.log("jam track selected with id " + data.item.id);
|
||||
});
|
||||
|
||||
$user.bind('railsAutocomplete.select', function(event, data){
|
||||
$userVal.val('');
|
||||
$userVal.val(data.item.id);
|
||||
console.log("user selected with id " + data.item.id);
|
||||
});
|
||||
}
|
||||
function handleUserLatencyForm(){
|
||||
var $userLatenciesForm = $('form#user_latencies_form');
|
||||
var $latenciesMyUser = $userLatenciesForm.find('#latencies_my_user');
|
||||
|
|
@ -106,5 +74,4 @@ function handleLatencyRecommendationForm(){
|
|||
$(document).ready(function() {
|
||||
handleUserLatencyForm();
|
||||
handleLatencyRecommendationForm();
|
||||
handleJamTrackRightsForm();
|
||||
});
|
||||
|
|
@ -2,97 +2,55 @@ class ArsesController < ApplicationController
|
|||
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
if params[:code] != Rails.application.config.data_dump_code
|
||||
render :json => {error: "Unauthorized"}, :status => 401
|
||||
return
|
||||
end
|
||||
|
||||
@arses = JamRuby::Ars.all
|
||||
render :json => @arses
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
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]
|
||||
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}"
|
||||
|
||||
# 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
|
||||
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?
|
||||
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
|
||||
|
||||
@ars = ars
|
||||
unless @ars.errors.any?
|
||||
if ars_id.nil?
|
||||
ars.reload
|
||||
ars_id = ars.id_int
|
||||
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
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -112,19 +112,6 @@ class Cohort < ActiveRecord::Base
|
|||
def self.cohort_users(cohort)
|
||||
User.where(created_at: cohort.group_start..cohort.group_end)
|
||||
end
|
||||
=begin
|
||||
SELECT played.user_id FROM
|
||||
(SELECT user_id, COUNT(*) cnt FROM music_sessions_user_history msuh1
|
||||
WHERE
|
||||
msuh1.created_at >= '2024-11-01' AND
|
||||
msuh1.created_at <= '202' AND
|
||||
EXTRACT(EPOCH FROM (msuh1.session_removed_at - msuh1.created_at)) >= 900 AND
|
||||
(SELECT COUNT(*) FROM music_sessions_user_history msuh2
|
||||
WHERE msuh1.music_session_id = msuh2.music_session_id
|
||||
) > 1
|
||||
GROUP BY user_id
|
||||
) played
|
||||
=end
|
||||
|
||||
def _played_online_subquery(constraint)
|
||||
where = if constraint.is_a?(Range)
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
BUNDLE_GEMFILE=Gemfile.alt RAILS_ENV=development LOCAL_DEV=1 MODERN_OS=1 JAM_RUBY_VERSION=2.4.1 bundle _1.17.3_ exec rails server -b 0.0.0.0 -p 3333
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
|
||||
class JamRuby::JamTrackRight
|
||||
attr_accessible :jam_track, :user, :jam_track_id_val, :user_id_val, as: :admin
|
||||
|
||||
def jam_track_id_val
|
||||
|
||||
end
|
||||
|
||||
def jam_track_id_val=(val)
|
||||
end
|
||||
|
||||
def user_id_val
|
||||
|
||||
end
|
||||
|
||||
def user_id_val=(val)
|
||||
end
|
||||
end
|
||||
|
|
@ -3,8 +3,4 @@ class JamRuby::JamTrack
|
|||
# add a custom validation
|
||||
|
||||
|
||||
def autocomplete_display_name
|
||||
"#{original_artist} - #{name}"
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -20,10 +20,6 @@ JamAdmin::Application.routes.draw do
|
|||
post :user_latencies, on: :collection
|
||||
post :user_latency_recommendation, on: :collection
|
||||
end
|
||||
|
||||
resources :jam_tracks do
|
||||
get :autocomplete_jam_track_name, :on => :collection
|
||||
end
|
||||
end
|
||||
|
||||
namespace :admin do
|
||||
|
|
@ -44,8 +40,6 @@ 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"
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
# trigger build
|
||||
|
|
@ -11,10 +11,10 @@ pipelines:
|
|||
- popd
|
||||
- pushd jam-ui/cicd
|
||||
- npm install
|
||||
- NODE_ENV=production PUBLIC_URL=https://staging.jamkazam.com REACT_APP_ORIGIN=staging.jamkazam.com REACT_APP_BASE_URL=https://staging.jamkazam.com REACT_APP_CLIENT_BASE_URL=https://staging.jamkazam.com REACT_APP_API_BASE_URL=https://staging.jamkazam.com/api REACT_APP_BITBUCKET_BUILD_NUMBER=$BITBUCKET_BUILD_NUMBER REACT_APP_BITBUCKET_COMMIT=$BITBUCKET_COMMIT REACT_APP_GOOGLE_ANALYTICS_ID=G-8W0GTL53NT ENVIRONMENT=staging ./generate.sh
|
||||
- NODE_ENV=production REACT_APP_ORIGIN=staging.jamkazam.com REACT_APP_CLIENT_BASE_URL=https://staging.jamkazam.com REACT_APP_API_BASE_URL=https://staging.jamkazam.com/api REACT_APP_BITBUCKET_BUILD_NUMBER=$BITBUCKET_BUILD_NUMBER REACT_APP_BITBUCKET_COMMIT=$BITBUCKET_COMMIT ENVIRONMENT=staging ./generate.sh
|
||||
- popd
|
||||
- cd jam-ui
|
||||
- NODE_ENV=production CI=false PUBLIC_URL=https://staging.jamkazam.com REACT_APP_ORIGIN=staging.jamkazam.com REACT_APP_CLIENT_BASE_URL=https://staging.jamkazam.com REACT_APP_BASE_URL=https://staging.jamkazam.com REACT_APP_API_BASE_URL=https://staging.jamkazam.com/api REACT_APP_BITBUCKET_BUILD_NUMBER=$BITBUCKET_BUILD_NUMBER REACT_APP_BITBUCKET_COMMIT=$BITBUCKET_COMMIT REACT_APP_GOOGLE_ANALYTICS_ID=G-8W0GTL53NT npm run build
|
||||
- NODE_ENV=production CI=false REACT_APP_ORIGIN=staging.jamkazam.com REACT_APP_CLIENT_BASE_URL=https://staging.jamkazam.com REACT_APP_API_BASE_URL=https://staging.jamkazam.com/api REACT_APP_BITBUCKET_BUILD_NUMBER=$BITBUCKET_BUILD_NUMBER REACT_APP_BITBUCKET_COMMIT=$BITBUCKET_COMMIT npm run build
|
||||
artifacts:
|
||||
- jam-ui/build/**
|
||||
- step:
|
||||
|
|
@ -58,10 +58,10 @@ pipelines:
|
|||
- popd
|
||||
- pushd jam-ui/cicd
|
||||
- npm install
|
||||
- NODE_ENV=production ENVIRONMENT=production PUBLIC_URL=https://www.jamkazam.com REACT_APP_ORIGIN=jamkazam.com REACT_APP_BASE_URL=https://www.jamkazam.com REACT_APP_CLIENT_BASE_URL=https://www.jamkazam.com REACT_APP_API_BASE_URL=https://www.jamkazam.com/api REACT_APP_BITBUCKET_BUILD_NUMBER=$BITBUCKET_BUILD_NUMBER REACT_APP_BITBUCKET_COMMIT=$BITBUCKET_COMMIT REACT_APP_GOOGLE_ANALYTICS_ID=G-SPTNJRW7WB ./generate.sh
|
||||
- NODE_ENV=production ENVIRONMENT=production REACT_APP_ORIGIN=jamkazam.com REACT_APP_CLIENT_BASE_URL=https://www.jamkazam.com REACT_APP_API_BASE_URL=https://www.jamkazam.com/api REACT_APP_BITBUCKET_BUILD_NUMBER=$BITBUCKET_BUILD_NUMBER REACT_APP_BITBUCKET_COMMIT=$BITBUCKET_COMMIT ./generate.sh
|
||||
- popd
|
||||
- cd jam-ui
|
||||
- NODE_ENV=production CI=false PUBLIC_URL=https://www.jamkazam.com REACT_APP_ORIGIN=jamkazam.com REACT_APP_BASE_URL=https://www.jamkazam.com REACT_APP_CLIENT_BASE_URL=https://www.jamkazam.com REACT_APP_API_BASE_URL=https://www.jamkazam.com/api REACT_APP_BITBUCKET_BUILD_NUMBER=$BITBUCKET_BUILD_NUMBER REACT_APP_BITBUCKET_COMMIT=$BITBUCKET_COMMIT REACT_APP_GOOGLE_ANALYTICS_ID=G-SPTNJRW7WB npm run build
|
||||
- NODE_ENV=production CI=false REACT_APP_ORIGIN=jamkazam.com REACT_APP_CLIENT_BASE_URL=https://www.jamkazam.com REACT_APP_API_BASE_URL=https://www.jamkazam.com/api REACT_APP_BITBUCKET_BUILD_NUMBER=$BITBUCKET_BUILD_NUMBER REACT_APP_BITBUCKET_COMMIT=$BITBUCKET_COMMIT npm run build
|
||||
|
||||
artifacts:
|
||||
- jam-ui/build/**
|
||||
|
|
|
|||
|
|
@ -9,8 +9,5 @@ REACT_APP_BITBUCKET_COMMIT=dev
|
|||
REACT_APP_ENV=development
|
||||
REACT_APP_RECAPTCHA_ENABLED=false
|
||||
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
|
||||
|
|
@ -7,7 +7,4 @@ REACT_APP_API_BASE_URL=http://www.jamkazam.local:3000/api
|
|||
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_RECURLY_PUBLIC_API_KEY=
|
||||
REACT_APP_BRAINTREE_TOKEN=
|
||||
REACT_APP_COOKIE_DOMAIN=.jamkazam.com
|
||||
|
|
@ -8,6 +8,3 @@ REACT_APP_ENV=production
|
|||
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
|
||||
|
|
|
|||
|
|
@ -8,6 +8,3 @@ REACT_APP_ENV=staging
|
|||
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,19 +38,3 @@ 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)
|
||||
|
|
|
|||
|
|
@ -10,49 +10,6 @@ const ArtistTemplatePageModule = require("./build/components/jamtracks/JKJamTrac
|
|||
var csvFilePath = `jamtracks-for-env/jam_tracks_for_jam_ui.${process.env.USER}.csv`
|
||||
var artistCsvFilePath = `jamtracks-for-env/jam_tracks_for_jam_ui_artists.${process.env.USER}.csv`
|
||||
|
||||
var sitemapPath = path.join(__dirname, "..", "public", "sitemap.xml");
|
||||
const clear_sitemap = () => {
|
||||
fs.writeFileSync(sitemapPath, "");
|
||||
|
||||
// Add the root element
|
||||
fs.writeFileSync(sitemapPath, `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n`, { flag: 'a' });
|
||||
|
||||
// Add the root url
|
||||
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}</loc></url>\n`, { flag: 'a' });
|
||||
|
||||
// Add standard URLs specific to this site, such as:
|
||||
// All prefix with /public
|
||||
|
||||
|
||||
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/</loc></url>\n`, { flag: 'a' });
|
||||
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/public/privacy</loc></url>\n`, { flag: 'a' } );
|
||||
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/public/help</loc></url>\n`, { flag: 'a' } );
|
||||
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/public/knowledge-base</loc></url>\n`, { flag: 'a' } );
|
||||
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/public/help-desk</loc></url>\n`, { flag: 'a' } );
|
||||
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/public/forum</loc></url>\n`, { flag: 'a' } );
|
||||
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/public/unsubscribe</loc></url>\n`, { flag: 'a' } );
|
||||
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/public/downloads</loc></url>\n`, { flag: 'a' } );
|
||||
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/public/downloads-legacy</loc></url>\n`, { flag: 'a' } );
|
||||
|
||||
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/auth/login</loc></url>\n`, { flag: 'a' });
|
||||
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/auth/signup</loc></url>\n`, { flag: 'a' } );
|
||||
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/auth/forget-password</loc></url>\n`, { flag: 'a' } );
|
||||
|
||||
// Add the closing root element
|
||||
}
|
||||
|
||||
const add_song_to_sitemap = (artistSlug, songSlug) => {
|
||||
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/backing-tracks/${artistSlug}/${songSlug}</loc></url>\n`, { flag: 'a' });
|
||||
}
|
||||
|
||||
const add_artist_to_sitemap = (artistSlug) => {
|
||||
fs.writeFileSync(sitemapPath, `<url><loc>${process.env.REACT_APP_BASE_URL}/backing-tracks/${artistSlug}</loc></url>\n`, { flag: 'a' });
|
||||
}
|
||||
|
||||
const close_sitemap = () => {
|
||||
fs.writeFileSync(sitemapPath, "</urlset>", { flag: 'a' } );
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a CSV file into an array of objects.
|
||||
* @param {string} csvPath - The path to the CSV file.
|
||||
|
|
@ -99,11 +56,9 @@ const init = () => {
|
|||
|
||||
if (!process.env.PUBLIC_URL) {
|
||||
console.log("setting public url", process.env.REACT_APP_BASE_URL);
|
||||
process.env.PUBLIC_URL = process.env.REACT_APP_BASE_URL;
|
||||
process.env.PUBLIC_URL = "";
|
||||
}
|
||||
|
||||
clear_sitemap();
|
||||
|
||||
|
||||
//const __dirname = path.resolve(path.dirname(''));
|
||||
|
||||
|
|
@ -139,8 +94,6 @@ const generateSongPages = async (render) => {
|
|||
const fullPath = process.env.REACT_APP_BASE_URL + location;
|
||||
const logoPath = process.env.REACT_APP_BASE_URL + "/favicon.svg";
|
||||
|
||||
add_song_to_sitemap(original_artist_slug, name_slug);
|
||||
|
||||
console.log(`Generating ${artist} - ${song}`);
|
||||
|
||||
const html = render
|
||||
|
|
@ -256,7 +209,6 @@ const generateArtistPages = async (render) => {
|
|||
const fullPath = process.env.REACT_APP_BASE_URL + location;
|
||||
const logoPath = process.env.REACT_APP_BASE_URL + "/favicon.svg";
|
||||
|
||||
add_artist_to_sitemap(original_artist_slug);
|
||||
console.log(`Generating ${artist}`);
|
||||
|
||||
const songs = matchingSongs.map((song) => {
|
||||
|
|
@ -320,8 +272,6 @@ const generateArtistPages = async (render) => {
|
|||
console.log(`Generated: ${outputFilePath}`);
|
||||
}
|
||||
|
||||
close_sitemap();
|
||||
|
||||
console.log("All pages generated!");
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ const Dotenv = require("dotenv-webpack");
|
|||
|
||||
const environment = process.env.ENVIRONMENT || 'development';
|
||||
const node_env = process.env.NODE_ENV || 'development';
|
||||
const debug = node_env == 'development';
|
||||
|
||||
module.exports = {
|
||||
entry: "./client-hydrate.js",
|
||||
output: {
|
||||
|
|
@ -42,7 +42,7 @@ module.exports = {
|
|||
react: path.resolve('./node_modules/react'),
|
||||
}
|
||||
},
|
||||
devtool: debug ? 'eval-source-map' : 'source-map',
|
||||
devtool: 'eval-source-map',
|
||||
mode: node_env,
|
||||
plugins: [
|
||||
new Dotenv({
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
/// <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,31 +1,23 @@
|
|||
/// <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 } });
|
||||
const currentUser = makeFakeUser({
|
||||
email: 'sam@example.com'
|
||||
});
|
||||
cy.stubAuthenticate({ ...currentUser });
|
||||
cy.intercept('POST', /\S+\/unsubscribe\/\S+/, { statusCode: 200, body: { ok: true } });
|
||||
beforeEach(() =>{
|
||||
cy.intercept('POST', /\S+\/unsubscribe_user_match\/\S+/, { statusCode: 200, body: { ok: true } });
|
||||
})
|
||||
|
||||
it("redirect to home page if tok is not provided", () => {
|
||||
cy.visit('/unsubscribe');
|
||||
cy.location('pathname').should('eq', '/errors/404');
|
||||
cy.location('pathname').should('eq', '/');
|
||||
});
|
||||
|
||||
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');
|
||||
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")
|
||||
});
|
||||
|
||||
})
|
||||
|
|
@ -2790,8 +2790,6 @@
|
|||
},
|
||||
"@fingerprintjs/fingerprintjs": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@fingerprintjs/fingerprintjs/-/fingerprintjs-4.6.0.tgz",
|
||||
"integrity": "sha512-g2z4lF2saGxVT+AQSmJhPSwW/hBn8vnFJMW6UYOMl9ipJT7re0RZbr2+lB2eCZj/lJ89wWc21FMA14v9iOKroQ==",
|
||||
"requires": {
|
||||
"tslib": "^2.4.1"
|
||||
}
|
||||
|
|
@ -16873,18 +16871,11 @@
|
|||
},
|
||||
"react-flatpickr": {
|
||||
"version": "3.10.6",
|
||||
"resolved": "https://registry.npmjs.org/react-flatpickr/-/react-flatpickr-3.10.6.tgz",
|
||||
"integrity": "sha512-Gg2zYFs+EoY7PNSk6lkiRyXV3nYmCp+ps29B4XFgZ1O4txOu6TvfHK2YCKT9zCpXbk5CcIGKMlvbupivWzy1+A==",
|
||||
"requires": {
|
||||
"flatpickr": "^4.6.2",
|
||||
"prop-types": "^15.5.10"
|
||||
}
|
||||
},
|
||||
"react-ga4": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-ga4/-/react-ga4-2.1.0.tgz",
|
||||
"integrity": "sha512-ZKS7PGNFqqMd3PJ6+C2Jtz/o1iU9ggiy8Y8nUeksgVuvNISbmrQtJiZNvC/TjDsqD0QlU5Wkgs7i+w9+OjHhhQ=="
|
||||
},
|
||||
"react-google-recaptcha": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-google-recaptcha/-/react-google-recaptcha-3.1.0.tgz",
|
||||
|
|
|
|||
|
|
@ -61,7 +61,6 @@
|
|||
"react-dropzone": "^10.2.2",
|
||||
"react-es6-progressbar.js": "^1.1.0",
|
||||
"react-flatpickr": "^3.10.6",
|
||||
"react-ga4": "^2.1.0",
|
||||
"react-google-recaptcha": "^3.1.0",
|
||||
"react-hook-form": "^7.11.1",
|
||||
"react-https-redirect": "^1.1.0",
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 55 KiB |
|
|
@ -1,20 +1,17 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import React from 'react';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import Layout from './layouts/JKLayout';
|
||||
import AnalyticsTracker from "./components/utilities/AnalyticsTracker";
|
||||
|
||||
import 'react-toastify/dist/ReactToastify.min.css';
|
||||
import 'react-datetime/css/react-datetime.css';
|
||||
import 'react-image-lightbox/style.css';
|
||||
|
||||
|
||||
const App = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<AnalyticsTracker />
|
||||
<Layout />
|
||||
</>
|
||||
|
||||
return (
|
||||
<Router basename={process.env.PUBLIC_URL}>
|
||||
<Layout />
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 55 KiB |
|
|
@ -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,31 +52,25 @@ 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) {
|
||||
console.log('adding jamtrack to cart');
|
||||
if(jamTrack){
|
||||
addJamTrackToCart();
|
||||
} else if (jamTrackArtistName) {
|
||||
console.log('redirecting to jamtracks artist landing');
|
||||
}else if(jamTrackArtistName){
|
||||
history.push(`/jamtracks?artist=${jamTrackArtistName}`);
|
||||
} else {
|
||||
console.log('redirecting to profile');
|
||||
history.push('/profile');
|
||||
}else{
|
||||
history.push('/public/downloads');
|
||||
}
|
||||
}
|
||||
}, [currentUser, jamTrack, jamTrackArtistName]);
|
||||
|
|
@ -136,9 +130,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>
|
||||
|
|
@ -146,7 +140,7 @@ const JKRegistrationForm = ({ hasLabel, jamTrack, jamTrackArtistName }) => {
|
|||
|
||||
<FormGroup>
|
||||
<Button color="primary" block className="mt-3" disabled={isDisabled}>
|
||||
{t('signup')}
|
||||
{t('signup')}
|
||||
</Button>
|
||||
</FormGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -33,9 +33,7 @@ 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,27 +1,11 @@
|
|||
import React, { Fragment, useEffect } from 'react';
|
||||
import React, { Fragment } from 'react';
|
||||
import { Col, Row } from 'reactstrap';
|
||||
import { Link, useHistory, useLocation } from 'react-router-dom';
|
||||
import { Link } 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,7 +37,6 @@ 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';
|
||||
|
|
@ -55,9 +54,6 @@ 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'));
|
||||
|
|
@ -133,6 +129,7 @@ function JKDashboardMain() {
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
const app = window.JK.JamKazam();
|
||||
|
||||
const jamServer = new window.JK.JamServer(app, function (event_type) {
|
||||
|
|
@ -144,8 +141,6 @@ 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...');
|
||||
|
|
@ -280,10 +275,6 @@ 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'}>
|
||||
|
|
@ -305,7 +296,6 @@ 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} />
|
||||
|
|
@ -322,8 +312,6 @@ 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>
|
||||
|
|
|
|||
|
|
@ -7,15 +7,15 @@ import {JamTrackPreviewProvider} from '../../context/JamTrackPreviewContext'
|
|||
import {useHistory} from "react-router-dom";
|
||||
import useJamTrackShopping from "../../hooks/useJamTrackShopping";
|
||||
import UserAuth, {useAuth} from '../../context/UserAuth';
|
||||
const awesome1 = '/img/landing/jamtracks/Top 10 Image - Number 1.webp';
|
||||
const awesome2 = '/img/landing/jamtracks/Top 10 Image - Number 2.webp';
|
||||
const awesome3 = '/img/landing/jamtracks/Top 10 Image - Number 3.webp';
|
||||
const awesome4 = '/img/landing/jamtracks/Top 10 Image - Number 4.webp';
|
||||
const awesome5 = '/img/landing/jamtracks/Top 10 Image - Number 5.webp';
|
||||
const awesome6 = '/img/landing/jamtracks/Top 10 Image - Number 6.webp';
|
||||
const awesome7 = '/img/landing/jamtracks/Top 10 Image - Number 7.webp';
|
||||
const awesome8 = '/img/landing/jamtracks/Top 10 Image - Number 8.webp';
|
||||
const awesome9 = '/img/landing/jamtracks/Top 10 Image - Number 9.webp';
|
||||
const awesome1 = '/img/landing/jamtracks/Top 10 Image - Number 1.png';
|
||||
const awesome2 = '/img/landing/jamtracks/Top 10 Image - Number 2.png';
|
||||
const awesome3 = '/img/landing/jamtracks/Top 10 Image - Number 3.png';
|
||||
const awesome4 = '/img/landing/jamtracks/Top 10 Image - Number 4.png';
|
||||
const awesome5 = '/img/landing/jamtracks/Top 10 Image - Number 5.png';
|
||||
const awesome6 = '/img/landing/jamtracks/Top 10 Image - Number 6.png';
|
||||
const awesome7 = '/img/landing/jamtracks/Top 10 Image - Number 7.png';
|
||||
const awesome8 = '/img/landing/jamtracks/Top 10 Image - Number 8.png';
|
||||
const awesome9 = '/img/landing/jamtracks/Top 10 Image - Number 9.png';
|
||||
// To test this component in isolation, go to:
|
||||
// http://beta.jamkazam.local:4000/public/backing-tracks/artist/song
|
||||
// But this is only for development
|
||||
|
|
@ -97,14 +97,6 @@ const ArtistBodyComponent = ({
|
|||
//coming to signup page after clicking on jamtrack or artist
|
||||
useEffect(() => {
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
document
|
||||
.querySelectorAll('iframe[data-src]')
|
||||
.forEach((iframe) => {
|
||||
iframe.src = iframe.dataset.src;
|
||||
});
|
||||
});
|
||||
|
||||
const checkOverflow = () => {
|
||||
if (jamtrackListingRef.current) {
|
||||
const element = jamtrackListingRef.current;
|
||||
|
|
@ -593,7 +585,7 @@ use their favorite plugins to power their percussive audio.</span></p>
|
|||
<div className="card-body bg-light">
|
||||
<div className="video-wrapper awesome-image-right">
|
||||
<div className="video-container">
|
||||
<iframe data-src="//www.youtube.com/embed/euxDykIqUdo" border="0"
|
||||
<iframe src="//www.youtube.com/embed/euxDykIqUdo" border="0"
|
||||
allowFullScreen></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import {Navbar} from "reactstrap";
|
|||
import {topNavbarBreakpoint} from "../../config";
|
||||
import Logo from "../navbar/Logo";
|
||||
import {BrowserQueryProvider} from "../../context/BrowserQuery";
|
||||
import {initGA, logPageView } from "../../helpers/analytics";
|
||||
import Footer from "../footer/JKFooter";
|
||||
|
||||
const ArtistTemplatePage = ({ original_artist_slug, artist, songs}) => {
|
||||
|
|
@ -24,8 +23,6 @@ const ArtistTemplatePage = ({ original_artist_slug, artist, songs}) => {
|
|||
const paddingClass = greaterThan.sm ? "px-6" : "px-1";
|
||||
|
||||
useEffect(() => {
|
||||
initGA();
|
||||
//logPageView(window.location.pathname + window.location.search);
|
||||
//see if there is affiliate in query string and save it as cookie
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const affiliate = urlParams.get('affiliate');
|
||||
|
|
|
|||
|
|
@ -7,15 +7,15 @@ import {JamTrackPreviewProvider} from '../../context/JamTrackPreviewContext'
|
|||
import {useHistory} from "react-router-dom";
|
||||
import useJamTrackShopping from "../../hooks/useJamTrackShopping";
|
||||
import UserAuth, {useAuth} from '../../context/UserAuth';
|
||||
const awesome1 = '/img/landing/jamtracks/Top 10 Image - Number 1.webp';
|
||||
const awesome2 = '/img/landing/jamtracks/Top 10 Image - Number 2.webp';
|
||||
const awesome3 = '/img/landing/jamtracks/Top 10 Image - Number 3.webp';
|
||||
const awesome4 = '/img/landing/jamtracks/Top 10 Image - Number 4.webp';
|
||||
const awesome5 = '/img/landing/jamtracks/Top 10 Image - Number 5.webp';
|
||||
const awesome6 = '/img/landing/jamtracks/Top 10 Image - Number 6.webp';
|
||||
const awesome7 = '/img/landing/jamtracks/Top 10 Image - Number 7.webp';
|
||||
const awesome8 = '/img/landing/jamtracks/Top 10 Image - Number 8.webp';
|
||||
const awesome9 = '/img/landing/jamtracks/Top 10 Image - Number 9.webp';
|
||||
const awesome1 = '/img/landing/jamtracks/Top 10 Image - Number 1.png';
|
||||
const awesome2 = '/img/landing/jamtracks/Top 10 Image - Number 2.png';
|
||||
const awesome3 = '/img/landing/jamtracks/Top 10 Image - Number 3.png';
|
||||
const awesome4 = '/img/landing/jamtracks/Top 10 Image - Number 4.png';
|
||||
const awesome5 = '/img/landing/jamtracks/Top 10 Image - Number 5.png';
|
||||
const awesome6 = '/img/landing/jamtracks/Top 10 Image - Number 6.png';
|
||||
const awesome7 = '/img/landing/jamtracks/Top 10 Image - Number 7.png';
|
||||
const awesome8 = '/img/landing/jamtracks/Top 10 Image - Number 8.png';
|
||||
const awesome9 = '/img/landing/jamtracks/Top 10 Image - Number 9.png';
|
||||
// To test this component in isolation, go to:
|
||||
// http://beta.jamkazam.local:4000/public/backing-tracks/artist/song
|
||||
// But this is only for development
|
||||
|
|
@ -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 = "acdc-back-in-black",
|
||||
slug = "ac-dc-back-in-black",
|
||||
artist = "AC/DC (defaulted)",
|
||||
song = "Back in Black (defaulted)",
|
||||
provided_jam_track = null
|
||||
|
|
@ -107,14 +107,6 @@ const BodyComponent = ({
|
|||
//coming to signup page after clicking on jamtrack or artist
|
||||
useEffect(() => {
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
document
|
||||
.querySelectorAll('iframe[data-src]')
|
||||
.forEach((iframe) => {
|
||||
iframe.src = iframe.dataset.src;
|
||||
});
|
||||
});
|
||||
|
||||
console.log("useEffect, currentUser", currentUser)
|
||||
updateHeight(); // Run after initial render
|
||||
|
||||
|
|
@ -555,7 +547,7 @@ use their favorite plugins to power their percussive audio.</span></p>
|
|||
<div className="card-body bg-light">
|
||||
<div className="video-wrapper awesome-image-right">
|
||||
<div className="video-container">
|
||||
<iframe data-src="//www.youtube.com/embed/euxDykIqUdo" border="0"
|
||||
<iframe src="//www.youtube.com/embed/euxDykIqUdo" border="0"
|
||||
allowFullScreen></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import {topNavbarBreakpoint} from "../../config";
|
|||
import Logo from "../navbar/Logo";
|
||||
import {BrowserQueryProvider} from "../../context/BrowserQuery";
|
||||
import Footer from "../footer/JKFooter";
|
||||
import {initGA, logPageView } from "../../helpers/analytics";
|
||||
|
||||
const TemplatePage = ({ id, plan_code, slug, artist, song, location}) => {
|
||||
console.log('TemplatePage', id, plan_code, slug, artist, song, location);
|
||||
|
|
@ -26,9 +25,6 @@ const TemplatePage = ({ id, plan_code, slug, artist, song, location}) => {
|
|||
const paddingClass = greaterThan.sm ? "px-6" : "px-1";
|
||||
|
||||
useEffect(() => {
|
||||
initGA();
|
||||
//logPageView( window.location.pathname + window.location.search);
|
||||
|
||||
//see if there is affiliate in query string and save it as cookie
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const affiliate = urlParams.get('affiliate');
|
||||
|
|
|
|||
|
|
@ -23,24 +23,12 @@ const ProfileDropdown = () => {
|
|||
|
||||
const handleLogout = async event => {
|
||||
event.preventDefault();
|
||||
const cookieDomain = `.${process.env.REACT_APP_ORIGIN}`;
|
||||
console.log('handleLogout: cookie: ', cookieDomain);
|
||||
removeCookie('remember_token', {
|
||||
domain: cookieDomain,
|
||||
path: '/'
|
||||
domain: `.${process.env.REACT_APP_ORIGIN}`
|
||||
});
|
||||
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 (
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { Link } from 'react-router-dom';
|
|||
import FalconCardHeader from '../common/FalconCardHeader';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { fetchSessions, fetchFriendsSessions, fetchPublicSessions, fetchInactiveSessions } from '../../store/features/sessionsSlice';
|
||||
import { fetchSessions } from '../../store/features/sessionsSlice';
|
||||
import { isIterableArray } from '../../helpers/utils';
|
||||
import { useResponsive } from '@farfetch/react-context-responsive';
|
||||
import JKModalDialog from '../common/JKModalDialog';
|
||||
|
|
@ -23,10 +23,7 @@ function JKMusicSessions() {
|
|||
const { nativeAppUnavailable, setNativeAppUnavailable } = useNativeApp();
|
||||
|
||||
useEffect(() => {
|
||||
// dispatch(fetchSessions());
|
||||
dispatch(fetchFriendsSessions());
|
||||
dispatch(fetchPublicSessions());
|
||||
//dispatch(fetchInactiveSessions());
|
||||
dispatch(fetchSessions());
|
||||
}, []);
|
||||
|
||||
const toggleAppUnavilableModel = () => {
|
||||
|
|
@ -40,7 +37,7 @@ function JKMusicSessions() {
|
|||
<CardBody className="pt-0">
|
||||
{loadingStatus === 'loading' && sessions.length === 0 ? (
|
||||
<Loader />
|
||||
) : loadingStatus === "succeeded" && sessions.length > 0 ? (
|
||||
) : isIterableArray(sessions) ? (
|
||||
<>
|
||||
{greaterThan.sm ? (
|
||||
<Row className="mb-3 justify-content-between d-none d-md-block">
|
||||
|
|
@ -54,7 +51,7 @@ function JKMusicSessions() {
|
|||
</Row>
|
||||
)}
|
||||
</>
|
||||
) : loadingStatus === "succeeded" && sessions.length <= 0 ? (
|
||||
) : (
|
||||
<Row className="p-card">
|
||||
<Col>
|
||||
{t('list.no_records_1', { ns: 'sessions' })}
|
||||
|
|
@ -62,13 +59,7 @@ function JKMusicSessions() {
|
|||
{t('list.no_records_2', { ns: 'sessions' })}
|
||||
</Col>
|
||||
</Row>
|
||||
) : loadingStatus === "failed" ? (
|
||||
<Row className="p-card">
|
||||
<Col>
|
||||
{t('list.error', { ns: 'sessions' })}
|
||||
</Col>
|
||||
</Row>
|
||||
) : null }
|
||||
)}
|
||||
</CardBody>
|
||||
</Card>
|
||||
<JKModalDialog
|
||||
|
|
|
|||
|
|
@ -1,480 +0,0 @@
|
|||
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;
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
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
|
||||
|
|
@ -116,17 +116,17 @@ const JKDownloads = () => {
|
|||
<div>
|
||||
<h5>Download the free JamKazam app</h5>
|
||||
{/* <p>{currentOS}</p> */}
|
||||
<p className="mb-lg-4">
|
||||
<p>
|
||||
You must use the JamKazam app to get into online sessions with other musicians. Our app also gives you the most feature-rich experience for JamTracks, recordings, live broadcasting, and other features. Click the button below to download the JamKazam app installer, then double click the installer to run it. For more detailed instructions,
|
||||
{isMacOS ? <a href="https://jamkazam.freshdesk.com/support/solutions/articles/66000130025" target='_blank'>see this help article</a> : isWindows && <a href="https://jamkazam.freshdesk.com/support/solutions/articles/66000130024" target="_blank">see this help article</a>}.
|
||||
</p>
|
||||
<div className='mt-2 d-flex flex-column flex-md-row'>
|
||||
<div style={ { flexGrow: 0, flexShrink: 0, flexBasis: '25%' } }>
|
||||
<a href={downloadLink} target='_blank' style={{ cursor: 'pointer' }}>
|
||||
<a href={downloadLink} target='_blank'>
|
||||
<img src={downloadImageUrl} alt="Download JamKazam" />
|
||||
</a>
|
||||
</div>
|
||||
<div className='mt-3 mt-lg-0 ml-lg-5' style={ { flexGrow: 1, flexShrink: 1, flexBasis: '75%' } }>
|
||||
<div className='mt-3 mt-lg-0 ml-lg-3' style={ { flexGrow: 1, flexShrink: 1, flexBasis: '75%' } }>
|
||||
<div>Need a different version?</div>
|
||||
<ul className='list-unstyled'>
|
||||
{availablePlatforms.filter(platform => platform !== currentOS).map(platform => (
|
||||
|
|
|
|||
|
|
@ -72,9 +72,8 @@ const JKDownloadsLegacy = () => {
|
|||
|
||||
const downloadLink = React.useMemo(() => {
|
||||
if (!currentOS) return null
|
||||
if (!selectedPlatform) return null
|
||||
return downloads[`JamClient/${selectedPlatform}`]
|
||||
}, [currentOS, selectedPlatform]);
|
||||
return downloads[`JamClient/${currentOS}`]
|
||||
}, [currentOS]);
|
||||
|
||||
const selectPlatform = (platform) => {
|
||||
setSelectedPlatform(platform)
|
||||
|
|
@ -121,17 +120,17 @@ const JKDownloadsLegacy = () => {
|
|||
<h5>Download the older legacy JamKazam app</h5>
|
||||
{/* <p>{currentOS}</p>
|
||||
<p>{selectedPlatform}</p> */}
|
||||
<p className="mb-lg-4">
|
||||
<p>
|
||||
You must use the JamKazam app to get into online sessions with other musicians. Our app also gives you the most feature-rich experience for JamTracks, recordings, live broadcasting, and other features. Click the button below to download the JamKazam app installer, then double click the installer to run it. For more detailed instructions,
|
||||
{isMacOS ? <a href="https://jamkazam.freshdesk.com/support/solutions/articles/66000130025" target="_blank">see this help article</a> : isWindows && <a href="https://jamkazam.freshdesk.com/support/solutions/articles/66000130024" target="_blank">see this help article</a>}.
|
||||
</p>
|
||||
<div className='mt-2 d-flex flex-column flex-md-row'>
|
||||
<div style={ { flexGrow: 0, flexShrink: 0, flexBasis: '25%' } }>
|
||||
<a href={downloadLink} target='_blank' style={ { cursor: 'pointer' } }>
|
||||
<a href={downloadLink} target='_blank'>
|
||||
<img src={downloadImageUrl} alt="Download JamKazam" />
|
||||
</a>
|
||||
</div>
|
||||
<div className='mt-3 mt-lg-0 ml-lg-5' style={ { flexGrow: 1, flexShrink: 1, flexBasis: '75%' } }>
|
||||
<div className='mt-3 mt-lg-0 ml-lg-3' style={ { flexGrow: 1, flexShrink: 1, flexBasis: '75%' } }>
|
||||
<div>Need a different version?</div>
|
||||
<ul className='list-unstyled'>
|
||||
{availablePlatforms.filter(platform => platform !== selectedPlatform).map(platform => (
|
||||
|
|
@ -143,7 +142,7 @@ const JKDownloadsLegacy = () => {
|
|||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className='mt-lg-4'>
|
||||
<div className='mt-1 mt-lg-4'>
|
||||
<div><strong>System Requirements</strong></div>
|
||||
{selectedPlatform === 'MacOSX' ? (
|
||||
<p>
|
||||
|
|
|
|||
|
|
@ -1,174 +0,0 @@
|
|||
import React, { useState, useEffect } from 'react'
|
||||
import { detectOS, isAppleSilicon } from '../../helpers/utils'
|
||||
import {getClientDownloads, getObsPluginDownloads} from '../../helpers/rest'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
const DownloadButtonMacAppleMx = '/img/downloads/Download-Button-Obs-Mac-Apple-Mx.png'
|
||||
const DownloadButtonMacIntel = '/img/downloads/Download-Button-Obs-Mac-Intel.png'
|
||||
const DownloadButtonWindows = '/img/downloads/Download-Button-Obs-Windows.png'
|
||||
|
||||
const JKObsDownloads = () => {
|
||||
const [currentOS, setCurrentOS] = useState(null)
|
||||
const [downloads, setDownloads] = useState({})
|
||||
|
||||
const availablePlatforms = React.useMemo(() => {
|
||||
const keys = Object.keys(downloads)
|
||||
//only show JamClientModern versions
|
||||
const sortedStrings = keys.filter(key => key.startsWith('OBSPlugin')).map(key => key.substring('OBSPlugin/'.length))
|
||||
|
||||
return sortedStrings
|
||||
}, [downloads])
|
||||
|
||||
const detectAndSetOS = () => {
|
||||
let os = detectOS()
|
||||
|
||||
if (os == "MacOSX") {
|
||||
const silicon = isAppleSilicon();
|
||||
if (silicon == true) {
|
||||
os = "MacOSX-M";
|
||||
}
|
||||
else {
|
||||
os = "MacOSX-Intel";
|
||||
}
|
||||
}
|
||||
if (!os) {
|
||||
os = "Win32"
|
||||
}
|
||||
setCurrentOS(os)
|
||||
}
|
||||
|
||||
const fetchClientDownloads = () => {
|
||||
getObsPluginDownloads()
|
||||
.then(resp => {
|
||||
if (resp.status === 200) {
|
||||
return resp.json()
|
||||
}
|
||||
}).then(data => {
|
||||
const platforms = Object.keys(data)
|
||||
const downloadUris = {}
|
||||
platforms.filter(p => p === 'OBSPlugin/MacOSX-M' || p === 'OBSPlugin/MacOSX-Intel' || p === 'OBSPlugin/Win32').forEach(platform => {
|
||||
downloadUris[platform] = data[platform].uri
|
||||
})
|
||||
setDownloads(downloadUris)
|
||||
detectAndSetOS()
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchClientDownloads()
|
||||
}, [])
|
||||
|
||||
const downloadLink = React.useMemo(() => {
|
||||
if (!currentOS) return null
|
||||
return downloads[`OBSPlugin/${currentOS}`]
|
||||
}, [currentOS]);
|
||||
|
||||
const selectPlatform = (platform) => {
|
||||
console.log('_DEBUG_ platform', platform)
|
||||
setCurrentOS(platform)
|
||||
}
|
||||
|
||||
const downloadImageUrl = React.useMemo(() => {
|
||||
if (!currentOS) return null
|
||||
switch (currentOS) {
|
||||
case 'MacOSX-M':
|
||||
return DownloadButtonMacAppleMx
|
||||
case 'MacOSX-Intel':
|
||||
return DownloadButtonMacIntel
|
||||
case 'Win32':
|
||||
return DownloadButtonWindows
|
||||
}
|
||||
}, [currentOS])
|
||||
|
||||
const isMacOS = React.useMemo(() => {
|
||||
if (!currentOS) return false
|
||||
return currentOS.startsWith('MacOSX')
|
||||
}, [currentOS])
|
||||
|
||||
|
||||
const isWindows = React.useMemo(() => {
|
||||
if (!currentOS) return false
|
||||
return currentOS === 'Win32'
|
||||
}, [currentOS])
|
||||
|
||||
const platformDisplayName = (platform) => {
|
||||
switch (platform) {
|
||||
case 'MacOSX-M':
|
||||
return 'JamKazam for Mac (Apple Silicon)'
|
||||
case 'MacOSX-Intel':
|
||||
return 'JamKazam for Mac (Intel)'
|
||||
case 'Win32':
|
||||
return 'JamKazam for Windows'
|
||||
}
|
||||
}
|
||||
|
||||
const onClickPlatform = (event, platform) => {
|
||||
event.preventDefault()
|
||||
selectPlatform(platform)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{Object.keys(downloads).length > 0 && currentOS ? (
|
||||
<div>
|
||||
<h5>Download and install OBS Studio</h5>
|
||||
{/* <p>{currentOS}</p> */}
|
||||
<p>
|
||||
|
||||
Before installing the JamKazam plugin for OBS, you must first download and install OBS Studio.
|
||||
If you have not already done so, <a target="_blank" href="https://jamkazam.freshdesk.com/support/solutions/articles/66000525817">follow the instructions in this help article</a> to download
|
||||
and install OBS Studio version 30.2.3. Please note that you cannot use OBS video features with the legacy
|
||||
JamKazam application - only with the current version of our app.
|
||||
</p>
|
||||
<h5 className="mt-lg-4">Download and install JamKazam plugin for OBS</h5>
|
||||
<p className="mb-lg-4">
|
||||
After installing OBS Studio as explained above, you'll need to install the JamKazam
|
||||
plugin for OBS. This plugin lets JamKazam and OBS Studio work together. Click the button
|
||||
below to download the plugin installer. When the download is complete, double click the
|
||||
installer, and then follow the on-screen instructions to install the plugin. For more detailed
|
||||
instructions, <a target="_blank" href="https://jamkazam.freshdesk.com/support/solutions/articles/66000525817">see this help article</a>.
|
||||
</p>
|
||||
<div className='mt-2 d-flex flex-column flex-md-row'>
|
||||
<div style={ { flexGrow: 0, flexShrink: 0, flexBasis: '25%' } }>
|
||||
<a href={downloadLink} target='_blank'>
|
||||
<img src={downloadImageUrl} alt="Download JamKazam" />
|
||||
</a>
|
||||
</div>
|
||||
<div className='mt-3 mt-lg-0 ml-lg-5' style={ { flexGrow: 1, flexShrink: 1, flexBasis: '75%' } }>
|
||||
<div>Need a different version?</div>
|
||||
<ul className='list-unstyled'>
|
||||
{availablePlatforms.filter(platform => platform !== currentOS).map(platform => (
|
||||
<li key={platform}><a href="#" onClick={(e) => onClickPlatform(e, platform)}>{platformDisplayName(platform)}</a></li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className='mt-lg-4'>
|
||||
<h5>System Requirements</h5>
|
||||
{isMacOS ? (
|
||||
<p>
|
||||
To install the JamKazam plugin for OBS Studio:<br />
|
||||
- OBS Studio version 30.2.3 must already be installed on your computer <br />
|
||||
- Your Mac must be running macOS 10.15 (Catalina) or higher <br />
|
||||
</p>
|
||||
) : isWindows ? (
|
||||
<p>
|
||||
To install the JamKazam plugin for OBS Studio<br />
|
||||
- OBS Studio version 30.2.3 must already be installed on your computer <br />
|
||||
- Your computer must be running Windows 10 or 11, 64-bit (32-bit not supported)
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div>Loading...</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default JKObsDownloads
|
||||
|
|
@ -1,18 +1,16 @@
|
|||
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, useParams } from "react-router-dom";
|
||||
import { useHistory } from "react-router-dom";
|
||||
|
||||
|
||||
const unsubscribeFromNewUsersWeeklyEmail = (token) => {
|
||||
|
||||
|
||||
|
||||
const baseUrl = process.env.REACT_APP_CLIENT_BASE_URL
|
||||
|
||||
const baseUrl = process.env.REACT_APP_LEGACY_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) {
|
||||
|
|
@ -24,64 +22,43 @@ 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("unsubscribe");
|
||||
const {t} = useTranslation()
|
||||
const queryObj = useBrowserQuery();
|
||||
const history = useHistory()
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [success, setSuccess] = useState(false)
|
||||
const [error, setError] = useState(null)
|
||||
const { tok } = useParams();
|
||||
|
||||
useEffect(() => {
|
||||
if (tok) {
|
||||
unsubscribe(tok)
|
||||
.then((resp) => {
|
||||
if (resp.ok) {
|
||||
setSuccess(true)
|
||||
} else {
|
||||
setSuccess(false)
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
setError(error)
|
||||
}).finally(() => {
|
||||
setLoading(false)
|
||||
});
|
||||
} else {
|
||||
const token = queryObj.get('tok')
|
||||
if(token){
|
||||
unsubscribeFromNewUsersWeeklyEmail(token)
|
||||
.then((resp) => {
|
||||
if(resp.ok){
|
||||
setSuccess(true)
|
||||
}
|
||||
})
|
||||
.catch(error => console.error(error))
|
||||
}else{
|
||||
history.push('/')
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
return (
|
||||
|
||||
<Card style={{ width: '25rem', margin: '2rem auto' }}>
|
||||
|
||||
|
||||
<Card color={ success ? 'success' : 'light' } style={{ width: '25rem', margin: '2rem auto' }}>
|
||||
<CardBody>
|
||||
<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>}
|
||||
</>
|
||||
|
||||
<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>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import useNativeAppCheck from '../../hooks/useNativeAppCheck';
|
|||
import { useNativeApp } from '../../context/NativeAppContext';
|
||||
import EnterIcon from '../../icons/enter.svg';
|
||||
import JKInstrumentIcon from '../profile/JKInstrumentIcon';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import {useHistory} from 'react-router-dom';
|
||||
import useSessionHelper from './JKUseSessionHelper';
|
||||
import JKModalDialog from '../common/JKModalDialog';
|
||||
import JKAppLaunch from '../page/JKAppLaunch';
|
||||
|
|
@ -25,10 +25,10 @@ function JKSession({ session }) {
|
|||
const { greaterThan } = useResponsive();
|
||||
const { setNativeAppUnavailable } = useNativeApp();
|
||||
const { sessionDescription } = useSessionHelper(session);
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const otherUserIds = session.active_music_session ? session.active_music_session.participants.map(p => p.user.id) : [];
|
||||
const otherUserIds = session.participants.map(p => p.user.id);
|
||||
const options = { currentUserId: currentUser.id, otherUserIds };
|
||||
dispatch(fetchUserLatencies(options));
|
||||
}, [session.id]);
|
||||
|
|
@ -40,7 +40,7 @@ function JKSession({ session }) {
|
|||
};
|
||||
|
||||
const hasFriendNote = session => {
|
||||
if (session.active_music_session && session.active_music_session.participants.find(p => p.user.is_friend)) {
|
||||
if (session.participants.find(p => p.user.is_friend)) {
|
||||
return t('list.notes.has_friend', { ns: 'sessions' });
|
||||
}
|
||||
};
|
||||
|
|
@ -71,62 +71,48 @@ function JKSession({ session }) {
|
|||
</div>
|
||||
<div>{sessionDescription}</div>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
{session.active_music_session && session.active_music_session.participants.length > 0 && (
|
||||
<>
|
||||
{session.active_music_session.participants.map(participant => (
|
||||
<Row style={musicianRowStyle} key={participant.id}>
|
||||
<Col>
|
||||
<JKSessionUser user={participant.user} />
|
||||
</Col>
|
||||
</Row>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
||||
{session.participants.map(participant => (
|
||||
<Row style={musicianRowStyle} key={participant.id}>
|
||||
<Col>
|
||||
<JKSessionUser user={participant.user} />
|
||||
</Col>
|
||||
</Row>
|
||||
))}
|
||||
</td>
|
||||
<td className="text-center">
|
||||
{session.active_music_session && session.active_music_session.participants.length > 0 && (
|
||||
<>
|
||||
{session.active_music_session.participants.map(participant => (
|
||||
<Row key={participant.id} style={musicianRowStyle}>
|
||||
<Col>
|
||||
<JKUserLatencyBadge key={participant.id} user={participant.user} showBadgeOnly={true} />
|
||||
</Col>
|
||||
</Row>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
{session.participants.map(participant => (
|
||||
<Row key={participant.id} style={musicianRowStyle}>
|
||||
<Col>
|
||||
<JKUserLatencyBadge key={participant.id} user={participant.user} showBadgeOnly={true} />
|
||||
</Col>
|
||||
</Row>
|
||||
))}
|
||||
</td>
|
||||
<td>
|
||||
{session.active_music_session && session.active_music_session.participants.length > 0 && (
|
||||
<>
|
||||
{session.active_music_session.participants.map(participant => (
|
||||
<Row style={musicianRowStyle} key={participant.id} data-testid={`Participant${participant.id}Tracks`}>
|
||||
<Col>
|
||||
{participant.tracks.map(track => (
|
||||
<span key={track.id} className="mr-1 mb-1" title={track.instrment}>
|
||||
<a
|
||||
id={`Participant${participant.id}Track${track.id}Instrument`}
|
||||
data-testid={`Track${track.id}Instrument`}
|
||||
>
|
||||
{/* <InstrumentIcon track={track} /> */}
|
||||
<JKInstrumentIcon instrumentId={track.instrument_id} instrumentName={track.instrument} />
|
||||
</a>
|
||||
<UncontrolledTooltip
|
||||
placement="top"
|
||||
target={`Participant${participant.id}Track${track.id}Instrument`}
|
||||
>
|
||||
{track.instrument}
|
||||
</UncontrolledTooltip>
|
||||
</span>
|
||||
))}
|
||||
</Col>
|
||||
</Row>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
{session.participants.map(participant => (
|
||||
<Row style={musicianRowStyle} key={participant.id} data-testid={`Participant${participant.id}Tracks`}>
|
||||
<Col>
|
||||
{participant.tracks.map(track => (
|
||||
<span key={track.id} className="mr-1 mb-1" title={track.instrment}>
|
||||
<a
|
||||
id={`Participant${participant.id}Track${track.id}Instrument`}
|
||||
data-testid={`Track${track.id}Instrument`}
|
||||
>
|
||||
{/* <InstrumentIcon track={track} /> */}
|
||||
<JKInstrumentIcon instrumentId={track.instrument_id} instrumentName={track.instrument} />
|
||||
</a>
|
||||
<UncontrolledTooltip
|
||||
placement="top"
|
||||
target={`Participant${participant.id}Track${track.id}Instrument`}
|
||||
>
|
||||
{track.instrument}
|
||||
</UncontrolledTooltip>
|
||||
</span>
|
||||
))}
|
||||
</Col>
|
||||
</Row>
|
||||
))}
|
||||
</td>
|
||||
<td className="text-center">
|
||||
<JoinSessionButton session={session} />
|
||||
|
|
@ -156,13 +142,11 @@ function JKSession({ session }) {
|
|||
<strong>{t('list.header.latency', { ns: 'sessions' })}</strong>
|
||||
</div>
|
||||
</div>
|
||||
{session.active_music_session && session.active_music_session.participants.length > 0 && (
|
||||
<div>
|
||||
{session.active_music_session.participants.map(participant => (
|
||||
<JKSessionUser key={participant.id} user={participant.user} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
{session.participants.map(participant => (
|
||||
<JKSessionUser key={participant.id} user={participant.user} />
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-4 d-flex flex-row justify-content-center">
|
||||
<JoinSessionButton session={session} />
|
||||
</div>
|
||||
|
|
@ -203,23 +187,23 @@ function JoinSessionButton({ session }) {
|
|||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<Button
|
||||
data-testid="joinBtn"
|
||||
color="primary"
|
||||
onClick={joinSession}
|
||||
className="btn-join btn-sm mr-1 mb-1 pt-1 pb-1 pl-1 pr-1"
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
<img src={EnterIcon} alt="enter" className="mr-1" style={{ width: "23px" }} />
|
||||
</Button>
|
||||
<div className='d-none d-sm-block'>
|
||||
<a href="#" onClick={joinSession}>
|
||||
Join Session
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
data-testid="joinBtn"
|
||||
color="primary"
|
||||
onClick={joinSession}
|
||||
className="btn-join btn-sm mr-1 mb-1 pt-1 pb-1 pl-1 pr-1"
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
<img src={EnterIcon} alt="enter" className="mr-1" style={ { width: "23px" } } />
|
||||
</Button>
|
||||
<div className='d-none d-sm-block'>
|
||||
<a href="#" onClick={joinSession}>
|
||||
Join Session
|
||||
</a>
|
||||
</div>
|
||||
<JKModalDialog
|
||||
</div>
|
||||
<JKModalDialog
|
||||
show={showAppLauncher}
|
||||
onToggle={toggleAppLauncher}
|
||||
title={t('new.page_title', { ns: 'sessions' })}
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
// AnalyticsTracker.js
|
||||
import { useEffect } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { initGA, logPageView } from "../../helpers/analytics";
|
||||
|
||||
const AnalyticsTracker = () => {
|
||||
const location = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
initGA();
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
logPageView(location.pathname + location.search);
|
||||
}, [location]);
|
||||
|
||||
return null; // it doesn't render anything
|
||||
};
|
||||
|
||||
export default AnalyticsTracker;
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
/**
|
||||
* 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;
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
import ReactGA from "react-ga4";
|
||||
|
||||
var initialized = false;
|
||||
|
||||
// Noticed that ReactGA.initialize logs a page view,
|
||||
// so the 1st logPageView will end up double-counting the initial path landed on
|
||||
var ignoredFirstPageView = false;
|
||||
|
||||
export const initGA = () => {
|
||||
try {
|
||||
if (process.env.REACT_APP_GOOGLE_ANALYTICS_ID) {
|
||||
ReactGA.initialize(process.env.REACT_APP_GOOGLE_ANALYTICS_ID);
|
||||
initialized = true;
|
||||
} else {
|
||||
initialized = false;
|
||||
console.log("no GA ID found");
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.log("Error initializing GA", error);
|
||||
}
|
||||
};
|
||||
|
||||
export const logPageView = (path) => {
|
||||
if(initialized) {
|
||||
if(ignoredFirstPageView) {
|
||||
try {
|
||||
ReactGA.send({hitType: "pageview", page: path});
|
||||
}
|
||||
catch(error) {
|
||||
console.log("Error logging page view", error);
|
||||
}
|
||||
}
|
||||
else {
|
||||
ignoredFirstPageView = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -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))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -213,30 +213,6 @@ export const getSessions = () => {
|
|||
});
|
||||
};
|
||||
|
||||
export const getFriendsSessions = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
apiFetch(`/sessions/friends`)
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
};
|
||||
|
||||
export const getPublicSessions = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
apiFetch(`/sessions/public`)
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
};
|
||||
|
||||
export const getInactiveSessions = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
apiFetch(`/sessions/inactive`)
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
};
|
||||
|
||||
export const getSessionsHistory = (options = {}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
apiFetch(`/sessions/history?${new URLSearchParams(options)}`)
|
||||
|
|
@ -289,7 +265,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_alt`, {
|
||||
apiFetch(`/users/${userId}/update_email`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ update_email: email, current_password })
|
||||
})
|
||||
|
|
@ -339,7 +315,7 @@ export const requstResetForgotPassword = email => {
|
|||
})
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const resetForgotPassword = (options = {}) => {
|
||||
|
|
@ -491,16 +467,18 @@ 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/public/${slug}`)
|
||||
apiFetch(`/jamtracks/${slug}`, {
|
||||
baseUrl: process.env.REACT_APP_CLIENT_BASE_URL
|
||||
})
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
|
|
@ -552,18 +530,6 @@ 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`, {
|
||||
|
|
@ -681,7 +647,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));
|
||||
|
|
@ -695,13 +661,6 @@ export const getClientDownloads = () => {
|
|||
.catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
export const getObsPluginDownloads = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
apiFetch(`/artifacts/OBSPlugin`)
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
//paypalPlaceOrder
|
||||
export const paypalPlaceOrder = (options = {}) => {
|
||||
|
|
@ -714,61 +673,3 @@ 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));
|
||||
});
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
const insertStylesheet = ({ isRTL, isDark }, cb) => {
|
||||
const link = document.createElement('link');
|
||||
//link.href = `${process.env.PUBLIC_URL}/css/theme${isDark ? '-dark' : ''}${isRTL ? '-rtl' : ''}.css`;
|
||||
link.href = `/css/theme${isDark ? '-dark' : ''}${isRTL ? '-rtl' : ''}.css`;
|
||||
link.href = `${process.env.PUBLIC_URL}/css/theme${isDark ? '-dark' : ''}${isRTL ? '-rtl' : ''}.css`;
|
||||
link.type = 'text/css';
|
||||
link.rel = 'stylesheet';
|
||||
link.className = 'theme-stylesheet';
|
||||
|
|
|
|||
|
|
@ -26,12 +26,6 @@
|
|||
"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.",
|
||||
|
|
@ -115,68 +109,5 @@
|
|||
"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."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,8 +40,7 @@
|
|||
},
|
||||
"no_records_1": "There are no public, open sessions currently available for you to join. We suggest you ",
|
||||
"create_session": "create a session",
|
||||
"no_records_2": " that others can join now, or wait a bit and then refresh this page in your browser to see if new public sessions have been started.",
|
||||
"error": "There was an error retrieving the session list. Please try again."
|
||||
"no_records_2": " that others can join now, or wait a bit and then refresh this page in your browser to see if new public sessions have been started."
|
||||
},
|
||||
"lobby": {
|
||||
"page_title": "Lobby",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
{
|
||||
"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..."
|
||||
"page_title": "Unsubscribe"
|
||||
}
|
||||
|
|
@ -4,8 +4,6 @@ import 'react-app-polyfill/stable';
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import HttpsRedirect from 'react-https-redirect';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
|
||||
import App from './App';
|
||||
import Main from './Main';
|
||||
import './helpers/initFA';
|
||||
|
|
@ -14,9 +12,7 @@ import './i18n/config';
|
|||
ReactDOM.render(
|
||||
<HttpsRedirect disabled={process.env.NODE_ENV === 'development'}>
|
||||
<Main>
|
||||
<Router>
|
||||
<App />
|
||||
</Router>
|
||||
<App />
|
||||
</Main>
|
||||
</HttpsRedirect>,
|
||||
document.getElementById('main')
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
import React, {useEffect} from 'react';
|
||||
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
|
||||
import { toast, ToastContainer } from 'react-toastify';
|
||||
import { CloseButton, Fade } from '../components/common/Toast';
|
||||
|
||||
|
|
@ -8,7 +8,6 @@ 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'));
|
||||
|
||||
|
|
@ -16,7 +15,6 @@ 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);
|
||||
|
|
@ -30,7 +28,7 @@ const Layout = () => {
|
|||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Router fallback={<span />}>
|
||||
<Switch>
|
||||
<Route path="/errors" component={ErrorLayout} />
|
||||
<Route path="/auth" component={AuthBasicLayout} />
|
||||
|
|
@ -39,7 +37,7 @@ const Layout = () => {
|
|||
</Switch>
|
||||
<ToastContainer transition={Fade} closeButton={<CloseButton />} position={toast.POSITION.BOTTOM_RIGHT} />
|
||||
<BuildMeta />
|
||||
</>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,6 @@ import JKForum from '../components/public/help/JKForum';
|
|||
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';
|
||||
|
||||
|
|
@ -23,11 +21,9 @@ 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/:tok`} exact component={JKUnsubscribe} />
|
||||
<Route path={`${url}/confirm-email-change`} exact component={JKConfirmEmailChange} /> */}
|
||||
<Route path={`${url}/unsubscribe`} exact component={JKUnsubscribe} />
|
||||
<Route path={`${url}/downloads`} exact component={JKDownloads} />
|
||||
<Route path={`${url}/downloads-legacy`} exact component={JKDownloadsLegacy} />
|
||||
<Route path={`${url}/obs-plugin-download`} exact component={JKObsDownloads} />
|
||||
<Route path={`${url}/backing-tracks/:artist/:song`} component={JKJamTracksLanding} />
|
||||
<Route path={`${url}/backing-tracks/:artist`} component={JKJamTracksArtistLanding} />
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import { getSessions, getFriendsSessions, getPublicSessions, getInactiveSessions, getPersonById } from '../../helpers/rest'
|
||||
import { getSessions, getPersonById } from '../../helpers/rest'
|
||||
|
||||
const initialState = {
|
||||
sessions: [],
|
||||
|
|
@ -9,50 +9,27 @@ const initialState = {
|
|||
}
|
||||
|
||||
export const fetchSessions = createAsyncThunk(
|
||||
"session/fetchSessions",
|
||||
"session/fetchSessions",
|
||||
async (options, thunkAPI) => {
|
||||
const response = await getSessions();
|
||||
return response.json();
|
||||
}
|
||||
)
|
||||
|
||||
export const fetchFriendsSessions = createAsyncThunk(
|
||||
"session/fetchFriendsSessions",
|
||||
async (options, thunkAPI) => {
|
||||
const response = await getFriendsSessions(options);
|
||||
return response.json();
|
||||
}
|
||||
)
|
||||
|
||||
export const fetchPublicSessions = createAsyncThunk(
|
||||
"session/fetchPublicSessions",
|
||||
async (options, thunkAPI) => {
|
||||
const response = await getPublicSessions(options);
|
||||
return response.json();
|
||||
}
|
||||
)
|
||||
|
||||
export const fetchInactiveSessions = createAsyncThunk(
|
||||
"session/fetchInactiveSessions",
|
||||
async (options, thunkAPI) => {
|
||||
const response = await getInactiveSessions(options);
|
||||
return response.json();
|
||||
}
|
||||
)
|
||||
|
||||
export const fetchPerson = createAsyncThunk(
|
||||
'session/fetchPerson',
|
||||
'session/fetchPerson',
|
||||
async (options, thunkAPI) => {
|
||||
const { userId } = options
|
||||
const {userId} = options
|
||||
const response = await getPersonById(userId)
|
||||
return response.json()
|
||||
}
|
||||
, {
|
||||
condition: (options, { getState, extra }) => {
|
||||
const { session } = getState()
|
||||
const { userId } = options
|
||||
,{
|
||||
condition: (options, {getState, extra}) => {
|
||||
const {session} = getState()
|
||||
const {userId} = options
|
||||
const person = session.people.find(person => person.id === userId)
|
||||
if (person && person.website) {
|
||||
if(person && person.website){
|
||||
//only proceed if full data set for user has not been fetched. person.website is not included in the initial data fetching (i.e: in friends listing ).
|
||||
return false;
|
||||
}
|
||||
|
|
@ -64,9 +41,9 @@ export const SessionSlice = createSlice({
|
|||
name: "session",
|
||||
initialState,
|
||||
reducers: {
|
||||
addSession: (state) => { },
|
||||
updateSession: (state) => { },
|
||||
deleteSession: (state) => { },
|
||||
addSession: (state) => {},
|
||||
updateSession: (state) => {},
|
||||
deleteSession: (state) => {},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
|
|
@ -74,68 +51,28 @@ export const SessionSlice = createSlice({
|
|||
state.status = "loading";
|
||||
})
|
||||
.addCase(fetchSessions.fulfilled, (state, action) => {
|
||||
// add unique sessions to the array
|
||||
const records = new Set([...state.sessions, ...action.payload]);
|
||||
const unique = [];
|
||||
records.map(x => unique.filter(p => p.id === x.id).length > 0 ? null : unique.push(x))
|
||||
state.sessions = unique;
|
||||
state.error = null;
|
||||
console.log(action.payload);
|
||||
state.status = "succeeded";
|
||||
state.sessions = action.payload;
|
||||
})
|
||||
.addCase(fetchSessions.rejected, (state, action) => {
|
||||
state.status = 'failed'
|
||||
state.error = action.error.message
|
||||
})
|
||||
.addCase(fetchFriendsSessions.pending, (state, action) => {
|
||||
state.status = "loading";
|
||||
})
|
||||
.addCase(fetchFriendsSessions.fulfilled, (state, action) => {
|
||||
// add unique sessions to the array
|
||||
const records = new Set([...state.sessions, ...action.payload.sessions]);
|
||||
const unique = [];
|
||||
records.map(x => unique.filter(p => p.id === x.id).length > 0 ? null : unique.push(x))
|
||||
state.sessions = unique;
|
||||
state.error = null;
|
||||
state.status = "succeeded";
|
||||
})
|
||||
.addCase(fetchPublicSessions.pending, (state, action) => {
|
||||
state.status = "loading";
|
||||
})
|
||||
.addCase(fetchPublicSessions.fulfilled, (state, action) => {
|
||||
// add unique sessions to the array
|
||||
const records = new Set([...state.sessions, ...action.payload.sessions]);
|
||||
const unique = [];
|
||||
records.map(x => unique.filter(p => p.id === x.id).length > 0 ? null : unique.push(x))
|
||||
state.sessions = unique;
|
||||
state.error = null;
|
||||
state.status = "succeeded";
|
||||
})
|
||||
.addCase(fetchInactiveSessions.pending, (state, action) => {
|
||||
state.status = "loading";
|
||||
})
|
||||
.addCase(fetchInactiveSessions.fulfilled, (state, action) => {
|
||||
// add unique sessions to the array
|
||||
const records = new Set([...state.sessions, ...action.payload.sessions]);
|
||||
const unique = [];
|
||||
records.map(x => unique.filter(p => p.id === x.id).length > 0 ? null : unique.push(x))
|
||||
state.sessions = unique;
|
||||
state.error = null;
|
||||
state.status = "succeeded";
|
||||
state.error = action.error.message
|
||||
})
|
||||
.addCase(fetchPerson.fulfilled, (state, action) => {
|
||||
const person = state.people.find(person => person.id === action.payload.id)
|
||||
if (person) {
|
||||
if(person){
|
||||
const updated = {
|
||||
...person,
|
||||
...person,
|
||||
...action.payload
|
||||
}
|
||||
const objIndex = state.people.findIndex((p => p.id === updated.id));
|
||||
state.people[objIndex] = updated
|
||||
} else {
|
||||
}else{
|
||||
state.people.push(action.payload)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default SessionSlice.reducer;
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
class AddProfileCompleteColumnsToUsers < ActiveRecord::Migration
|
||||
def self.up
|
||||
execute "ALTER TABLE users ADD COLUMN profile_completed_at TIMESTAMP"
|
||||
#add index on profile_completed_at
|
||||
execute "CREATE INDEX index_users_on_profile_completed_at ON users USING btree (profile_completed_at)"
|
||||
execute "ALTER TABLE users ADD COLUMN profile_complete_reminder1_sent_at TIMESTAMP"
|
||||
execute "ALTER TABLE users ADD COLUMN profile_complete_reminder2_sent_at TIMESTAMP"
|
||||
execute "ALTER TABLE users ADD COLUMN profile_complete_reminder3_sent_at TIMESTAMP"
|
||||
|
||||
User.find_each(batch_size:100) do |user|
|
||||
User.where(id:user.id).update_all(profile_completed_at: Time.now)
|
||||
end
|
||||
#User.where('users.id IN (SELECT player_id FROM musicians_instruments) OR users.id IN (SELECT player_id FROM genre_players)').update_all(profile_completed_at: Time.now)
|
||||
end
|
||||
def self.down
|
||||
execute "ALTER TABLE users DROP COLUMN profile_completed_at"
|
||||
execute "ALTER TABLE users DROP COLUMN profile_complete_reminder1_sent_at"
|
||||
execute "ALTER TABLE users DROP COLUMN profile_complete_reminder2_sent_at"
|
||||
execute "ALTER TABLE users DROP COLUMN profile_complete_reminder3_sent_at"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
# Adds
|
||||
# affiliate_partners.jamtrack_cumulative_earnings_in_cents
|
||||
# affiliate_partners.subscriptions_cumulative_earnings_in_cents
|
||||
# affiliate_partners.subscriptions_count
|
||||
# affiliate_quarterly_payments.jamtrack_due_amount_in_cents
|
||||
# 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 AffiliateTrackingTotals < ActiveRecord::Migration
|
||||
|
||||
def self.up
|
||||
execute "ALTER TABLE affiliate_partners ADD COLUMN jamtrack_cumulative_earnings_in_cents INTEGER NOT NULL DEFAULT 0"
|
||||
execute "ALTER TABLE affiliate_partners ADD COLUMN subscriptions_cumulative_earnings_in_cents INTEGER NOT NULL DEFAULT 0"
|
||||
execute "ALTER TABLE affiliate_partners ADD COLUMN current_quarter_in_cents INTEGER NOT NULL DEFAULT 0"
|
||||
execute "ALTER TABLE affiliate_partners ADD COLUMN jamtrack_current_quarter_in_cents INTEGER NOT NULL DEFAULT 0"
|
||||
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 "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);"
|
||||
|
||||
end
|
||||
=begin
|
||||
|
||||
ALTER TABLE affiliate_partners ADD COLUMN jamtrack_cumulative_earnings_in_cents INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE affiliate_partners ADD COLUMN subscriptions_cumulative_earnings_in_cents INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE affiliate_partners ADD COLUMN current_quarter_in_cents INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE affiliate_partners ADD COLUMN jamtrack_current_quarter_in_cents INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE affiliate_partners ADD COLUMN subscriptions_current_quarter_in_cents INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE affiliate_partners ADD COLUMN jamtracks_sold INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE affiliate_partners ADD COLUMN subscriptions_count INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE affiliate_quarterly_payments ADD COLUMN jamtrack_due_amount_in_cents INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE affiliate_quarterly_payments ADD COLUMN subscription_due_amount_in_cents INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE affiliate_monthly_payments ADD COLUMN jamtrack_due_amount_in_cents INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE affiliate_monthly_payments ADD COLUMN subscription_due_amount_in_cents INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE affiliate_quarterly_payments ADD COLUMN subscriptions_count INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE affiliate_monthly_payments ADD COLUMN subscriptions_count INTEGER NOT NULL DEFAULT 0;
|
||||
CREATE INDEX affiliate_partner_user_id_idx ON affiliate_partners USING btree (partner_user_id);
|
||||
CREATE INDEX affiliate_quarterly_payments_closed_index ON affiliate_quarterly_payments USING btree (paid);
|
||||
CREATE INDEX affiliate_quarterly_payments_paid_index ON affiliate_quarterly_payments USING btree (closed);
|
||||
CREATE INDEX affiliate_monthly_payments_paid_index ON affiliate_monthly_payments USING btree (closed);
|
||||
|
||||
|
||||
=end
|
||||
def self.down
|
||||
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"
|
||||
end
|
||||
end
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
class AddGearSetupReminderColumnsToUsers < ActiveRecord::Migration
|
||||
def self.up
|
||||
execute "ALTER TABLE users ADD COLUMN gear_setup_reminder1_sent_at TIMESTAMP"
|
||||
execute "ALTER TABLE users ADD COLUMN gear_setup_reminder2_sent_at TIMESTAMP"
|
||||
execute "ALTER TABLE users ADD COLUMN gear_setup_reminder3_sent_at TIMESTAMP"
|
||||
end
|
||||
|
||||
def self.down
|
||||
execute "ALTER TABLE users DROP COLUMN gear_setup_reminder1_sent_at"
|
||||
execute "ALTER TABLE users DROP COLUMN gear_setup_reminder2_sent_at"
|
||||
execute "ALTER TABLE users DROP COLUMN gear_setup_reminder3_sent_at"
|
||||
end
|
||||
end
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
class AddSignupSurveySentAtToUsers < ActiveRecord::Migration
|
||||
def self.up
|
||||
execute "ALTER TABLE users ADD COLUMN signup_survey_sent_at TIMESTAMP"
|
||||
User.find_each(batch_size:100) do |user|
|
||||
User.where(id:user.id).update_all(signup_survey_sent_at: Time.now)
|
||||
end
|
||||
end
|
||||
def self.down
|
||||
execute "ALTER TABLE users DROP COLUMN signup_survey_sent_at"
|
||||
end
|
||||
end
|
||||
=begin
|
||||
ALTER TABLE users ADD COLUMN signup_survey_sent_at TIMESTAMP;
|
||||
CREATE INDEX index_users_on_signup_survey_sent_at ON users USING btree (signup_survey_sent_at);
|
||||
|
||||
User.find_each(batch_size:100) do |user|
|
||||
User.where(id:user.id).update_all(signup_survey_sent_at: Time.now)
|
||||
end
|
||||
=end
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
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
|
||||