VRFS-3936 merging develop

This commit is contained in:
Jonathan Kolyer 2017-02-24 14:05:57 -08:00
commit 81979ba4bb
145 changed files with 3503 additions and 1102 deletions

View File

@ -36,11 +36,11 @@ gem 'coffee-script-source' #, '~> 1.4.0' # ADD THIS LINE, 1.5.0 doesn't compile
gem 'devise' #, '3.3.0'
gem 'will_paginate' #, '3.0.3'
gem 'bootstrap-will_paginate', '0.0.6'
gem 'carrierwave' #, '0.9.0'
gem 'carrierwave', '0.11.2' #, '0.9.0'
gem 'carrierwave_direct'
gem 'uuidtools', '2.1.2'
gem 'jquery-ui-rails' #, '4.2.1'
gem 'jquery-rails'
gem 'jquery-ui-rails', '5.0.5' #, '4.2.1'
gem 'jquery-rails', '4.1.1' # both this and jquery-ui-rails are pinned; if you unpin, jquery/autocomplete is missing during precomplie
gem 'rails-jquery-autocomplete' # This is the maintained version of rails3-jquery-autocomplete
gem 'activeadmin' , '1.0.0.pre4'# github: 'activeadmin', branch: 'master'
gem 'mime-types', '1.25'

View File

@ -1,8 +1,12 @@
ActiveAdmin.register JamRuby::Jamblaster, :as => 'Jamblaster' do
ActiveAdmin.register JamRuby::Jamblaster, :as => 'Jamblaster' do
config.filters = false
menu :label => 'JamBlasters', :parent => 'JamBlaster'
scope("Connected", default: true) { |scope| scope.where('client_id in (select client_id from connections)') }
scope("All") { |scope| scope.order('created_at desc') }
form do |f|
f.inputs 'New JamBlaster' do
f.input :user, required: true, collection: User.all, include_blank: false
@ -12,4 +16,25 @@ ActiveAdmin.register JamRuby::Jamblaster, :as => 'Jamblaster' do
end
f.actions
end
index do
# actions # use this for all view/edit/delete links
column 'Serial' do |oo|
oo.serial_no
end
column 'IPv4' do |oo|
oo.ipv4_link_local
end
column 'IPv6' do |oo|
oo.ipv6_link_local
end
end
end

View File

@ -30,11 +30,17 @@ ActiveAdmin.register JamRuby::SaleLineItem, :as => 'Sale Line Items' do
end
end
column 'Source' do |oo|
oo.sale.source
if oo.sale.source == JamRuby::Sale::SOURCE_PAYPAL
link_to(oo.sale.source, 'https://history.paypal.com/webscr?cmd=_history-details-from-hub&id=' + oo.sale.recurly_invoice_id)
else
oo.sale.source
end
end
column 'When' do |oo|
oo.created_at
end
column 'Link' do |oo|
end
end

View File

@ -368,4 +368,8 @@ retailers.sql
second_ed.sql
second_ed_v2.sql
retailers_v2.sql
retailer_interest.sql
retailer_interest.sql
connection_role.sql
retailer_payment_split.sql
teacher_distribution_fields.sql
jam_track_download_rights.sql

View File

@ -0,0 +1,2 @@
ALTER TABLE connections ADD COLUMN client_role VARCHAR;
ALTER TABLE connections ADD COLUMN parent_client_id VARCHAR;

View File

@ -0,0 +1,8 @@
ALTER TABLE jam_tracks ADD COLUMN download_price numeric;
UPDATE jam_tracks SET download_price = 4.99;
ALTER TABLE jam_track_rights ADD COLUMN can_download BOOLEAN NOT NULL DEFAULT FALSE;
UPDATE jam_track_rights SET can_download = TRUE;
ALTER TABLE shopping_carts ADD COLUMN variant VARCHAR;
UPDATE shopping_carts set variant = 'stream' where cart_type = 'JamTrack';
ALTER TABLE sale_line_items ADD COLUMN variant VARCHAR;
UPDATE sale_line_items set variant = 'full';

View File

@ -0,0 +1,2 @@
ALTER TABLE retailers ADD COLUMN payment VARCHAR;
ALTER TABLE lesson_bookings ADD COLUMN payment VARCHAR;

View File

@ -0,0 +1,3 @@
ALTER TABLE teacher_distributions ADD COLUMN teacher_fee_in_cents INTEGER;
ALTER TABLE lesson_bookings ADD COLUMN same_retailer BOOLEAN DEFAULT FALSE NOT NULL;
ALTER TABLE lesson_sessions ADD COLUMN admin_marked BOOLEAN DEFAULT FALSE NOT NULL;

View File

@ -64,6 +64,7 @@ gem 'icalendar'
gem 'email_validator'
group :test do
gem 'pry'
gem 'simplecov', '~> 0.7.1'
gem 'simplecov-rcov'
gem 'factory_girl', '4.5.0'

View File

@ -40,6 +40,7 @@ require "jam_ruby/errors/state_error"
require "jam_ruby/errors/jam_argument_error"
require "jam_ruby/errors/jam_record_not_found"
require "jam_ruby/errors/conflict_error"
require "jam_ruby/errors/pay_pal_client_error"
require "jam_ruby/lib/app_config"
require "jam_ruby/lib/s3_manager_mixin"
require "jam_ruby/lib/s3_public_manager_mixin"

View File

@ -42,7 +42,7 @@
yourself playing along with the rest of the band in audio or video, and more. Get your first
JamTrack free to try one out! After that they are just $1.99 each. <a href="https://jamkazam.desk.com/customer/en/portal/articles/2414618-playing-with-jamtracks" style="color:#fc0">Click here for more
JamTrack free to try one out! After that they are just $1.99/$4.99 each. <a href="https://jamkazam.desk.com/customer/en/portal/articles/2414618-playing-with-jamtracks" style="color:#fc0">Click here for more
information</a> on how you can use JamTracks in your browser, in our free Mac or Windows

View File

@ -402,7 +402,7 @@ SQL
music_session.creator
end
def join_music_session(user, client_id, music_session, as_musician, tracks, audio_latency, video_sources=nil)
def join_music_session(user, client_id, music_session, as_musician, tracks, audio_latency, client_role = nil, parent_client_id = nil, video_sources=nil)
connection = nil
ConnectionManager.active_record_transaction do |connection_manager|
@ -418,7 +418,7 @@ SQL
raise JamPermissionError, "wrong user_id associated with connection #{client_id}"
end
connection.join_the_session(music_session, as_musician, tracks, user, audio_latency, video_sources)
connection.join_the_session(music_session, as_musician, tracks, user, audio_latency, client_role, parent_client_id, video_sources)
JamRuby::MusicSessionUserHistory.join_music_session(user.id, music_session.id, client_id)
# connection.music_session_id = music_session.id
# connection.as_musician = as_musician

View File

@ -0,0 +1,19 @@
module JamRuby
class PayPalClientError < StandardError
attr_accessor :errors
def initialize(data)
if data.respond_to?('has_key?')
self.errors = data
else
self.errors = {:message=>data.to_s}
end
end # initialize
def to_s
s=super
s << ", errors: #{errors.inspect}" if self.errors.any?
s
end
end
end

View File

@ -868,6 +868,7 @@ module JamRuby
jam_track.genres = determine_genres(metadata)
jam_track.language = determine_language(metadata)
jam_track.price = 1.99
jam_track.download_price = 4.99
jam_track.reproduction_royalty_amount = nil
jam_track.reproduction_royalty = true
jam_track.public_performance_royalty = true

View File

@ -501,7 +501,7 @@ module JamRuby
[music_sessions, user_scores]
end
def self.participant_create(user, music_session_id, client_id, as_musician, tracks, audio_latency, video_sources=nil)
def self.participant_create(user, music_session_id, client_id, as_musician, tracks, audio_latency, client_role = nil, parent_client_id = nil, video_sources=nil)
music_session = MusicSession.find(music_session_id)
# USERS ARE ALREADY IN SESSION
@ -514,7 +514,7 @@ module JamRuby
active_music_session.with_lock do # VRFS-1297
active_music_session.tick_track_changes
# VRFS-3986
connection = ConnectionManager.new.join_music_session(user, client_id, active_music_session, as_musician, tracks, audio_latency, video_sources)
connection = ConnectionManager.new.join_music_session(user, client_id, active_music_session, as_musician, tracks, audio_latency, client_role, parent_client_id, video_sources)
if connection.errors.any?
# rollback the transaction to make sure nothing is disturbed in the database
@ -573,7 +573,7 @@ module JamRuby
# auto-join this user into the newly created session
as_musician = true
connection = ConnectionManager.new.join_music_session(user, client_id, active_music_session, as_musician, tracks, audio_latency, video_sources)
connection = ConnectionManager.new.join_music_session(user, client_id, active_music_session, as_musician, tracks, audio_latency, client_role, parent_client_id, video_sources)
unless connection.errors.any?
user.update_progression_field(:first_music_session_at)

View File

@ -176,11 +176,13 @@ module JamRuby
true
end
def join_the_session(music_session, as_musician, tracks, user, audio_latency, videos=nil)
def join_the_session(music_session, as_musician, tracks, user, audio_latency, client_role = nil, parent_client_id = nil, videos=nil)
self.music_session_id = music_session.id
self.as_musician = as_musician == true # this is deliberate; otherwise we create a warning in one our tests that passes 'blarg' (rails warning about casting strings to false)
self.joining_session = true
self.joined_session_at = Time.now
self.client_role = client_role
self.parent_client_id = parent_client_id
associate_tracks(tracks) unless tracks.nil?
associate_videos(videos) unless videos.nil?
self.save

View File

@ -28,6 +28,10 @@ module JamRuby
sale_display
end
def variant_price(variant = nil)
price
end
def price
if card_type == JAM_TRACKS_5
10.00
@ -39,7 +43,7 @@ module JamRuby
end
def sale_display
def sale_display(variant = nil)
if card_type == JAM_TRACKS_5
'JamTracks Gift Card (5)'
elsif card_type == JAM_TRACKS_10

View File

@ -161,9 +161,22 @@ module JamRuby
true
end
def sale_display
"JamTrack: " + name
def sale_display(variant = nil)
if variant == ShoppingCart::JAMTRACK_FULL
variant_desc = 'FULL'
elsif variant == ShoppingCart::JAMTRACK_DOWNLOAD
variant_desc = 'UPRGADE'
elsif variant == ShoppingCart::JAMTRACK_STREAM
variant_desc = 'FOR USE ONLY WITHIN APP'
else
variant_desc = 'UNKNOWN'
end
"JamTrack: #{name} - #{variant_desc}"
end
def duplicate_positions?
counter = {}
jam_track_tracks.each do |track|
@ -504,14 +517,43 @@ module JamRuby
owners.include?(user)
end
def right_for_user(user)
jam_track_rights.where("user_id=?", user).first
def right_for_user(user, variant = nil)
query = jam_track_rights.where("user_id=?", user)
if variant
if variant == ShoppingCart::JAMTRACK_DOWNLOAD
query = query.where('can_download', true)
elsif variant == ShoppingCart::JAMTRACK_FULL
query = query.where('can_download', true)
elsif variant == ShoppingCart::JAMTRACK_STREAM
else
throw 'unknown variant ' + variant
end
end
query.first
end
def mixdowns_for_user(user)
JamTrackMixdown.where(user_id: user.id).where(jam_track_id: self.id)
end
def upgrade_price
variant_price('download')
end
def variant_price(variant)
if variant == 'full'
download_price
elsif variant == 'download'
download_price - price
else
price
end
end
def short_plan_code
prefix = 'jamtrack-'
plan_code[prefix.length..-1]

View File

@ -45,6 +45,7 @@ module JamRuby
belongs_to :default_slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :default_slot_id, inverse_of: :defaulted_booking, :dependent => :destroy
belongs_to :counter_slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :counter_slot_id, inverse_of: :countered_booking, :dependent => :destroy
belongs_to :school, class_name: "JamRuby::School"
belongs_to :retailer, class_name: "JamRuby::Retailer"
belongs_to :test_drive_package_choice, class_name: "JamRuby::TestDrivePackageChoice"
belongs_to :posa_card, class_name: "JamRuby::PosaCard"
has_many :lesson_booking_slots, class_name: "JamRuby::LessonBookingSlot", :dependent => :destroy
@ -527,11 +528,15 @@ module JamRuby
end
end
def distribution_price_in_cents(target, education)
def distribution_price_in_cents(target, education, split = nil, fee_rate = nil)
distribution = teacher_distribution_price_in_cents(target)
if education
(distribution * 0.0625).round
if split
(distribution * split).round
# when a split is provided, we also pin down the teacher_fee_in_cents, instead of assuming a bunch of stuff
elsif education
(distribution * 0.0625).round # 0.0625 is 1/4th of 25%
else
distribution
end
@ -799,6 +804,13 @@ module JamRuby
lesson_booking.school = lesson_booking.teacher.teacher.school
end
# copy payment settings from retailer into lesson booking
if lesson_booking.teacher && lesson_booking.teacher.teacher.retailer
lesson_booking.retailer = lesson_booking.teacher.teacher.retailer
lesson_booking.payment = lesson_booking.teacher.teacher.retailer.payment_details.to_json
lesson_booking.same_retailer = lesson_booking.teacher.teacher.retailer.affiliate_partner == user.affiliate_referral
end
if user
lesson_booking.same_school = !!(lesson_booking.school && user.school && (lesson_booking.school.id == user.school.id))
if lesson_booking.same_school

View File

@ -53,11 +53,11 @@ module JamRuby
end
def teacher_distribution
teacher_distributions.where(education:false).first
teacher_distributions.where(education: false).first
end
def education_distribution
teacher_distributions.where(education:true).first
teacher_distributions.where(education: true).first
end
def add_test_drives
@ -97,15 +97,46 @@ module JamRuby
purchase.month = month
purchase.recurring = true
# this is for monthly
if lesson_booking && lesson_booking.requires_teacher_distribution?(purchase)
teacher_dist = TeacherDistribution.create_for_lesson_package_purchase(purchase, false)
purchase.teacher_distributions << teacher_dist
# price should always match the teacher_distribution, if there is one
purchase.price = teacher_dist.amount_in_cents / 100
if lesson_booking.school_on_school_payment?
purchase.teacher_distributions << TeacherDistribution.create_for_lesson_package_purchase(purchase, true)
if lesson_booking.posa_card.nil? && lesson_booking.payment
# if there is a payment object, it will describe how everything gets doled out
payment = JSON.parse(lesson_booking.payment)
teacher_split = payment["teacher"]
retailer_split = payment["retailer"]
retailer_rate = teacher.teacher.retailer.jamkazam_rate + APP_CONFIG.stripe[:charge_fee] # add 0.03 to account for stripe deduction
if teacher_split && teacher_split > 0
teacher_dist = TeacherDistribution.create_for_lesson_package_purchase(purchase, false, (teacher_split / 100.0).round(2), retailer_rate)
purchase.teacher_distributions << teacher_dist
# price should always match the teacher_distribution, if there is one
purchase.price = teacher_dist.amount_in_cents / 100
end
if retailer_split && retailer_split > 0
teacher_dist = TeacherDistribution.create_for_lesson_package_purchase(purchase, false, (retailer_split / 100.0).round(2), retailer_rate)
teacher_dist.retailer = teacher.teacher.retailer
teacher_dist.save
purchase.teacher_distributions << teacher_dist
end
else
teacher_dist = TeacherDistribution.create_for_lesson_package_purchase(purchase, false)
purchase.teacher_distributions << teacher_dist
# price should always match the teacher_distribution, if there is one
purchase.price = teacher_dist.amount_in_cents / 100
if lesson_booking.school_on_school_payment?
teacher_dist = TeacherDistribution.create_for_lesson_package_purchase(purchase, true)
purchase.teacher_distributions << teacher_dist
end
end
end
else

View File

@ -102,10 +102,13 @@ module JamRuby
end
def sale_display
def sale_display(variant = nil)
name
end
def variant_price(variant = nil)
price
end
def plan_code
if package_type == SINGLE_FREE
"lesson-package-single-free"

View File

@ -97,13 +97,17 @@ module JamRuby
end
def teacher_distribution
teacher_distributions.where(education:false).first
teacher_distributions.where(education:false).where('retailer_id is null').first
end
def education_distribution
teacher_distributions.where(education:true).first
end
def retailer_distribution
teacher_distributions.where('retailer_id is not null').first
end
def manage_slot_changes
# if this slot changed, we need to update the time. But LessonBooking does this for us, for requested/accepted .
# TODO: what to do, what to do.
@ -197,6 +201,59 @@ module JamRuby
counterer_id.nil? || counterer_id == student_id
end
def mark_lesson(success, administratively_marked = false)
self.success = success
self.analysed_at = Time.now
self.analysed = true
self.status = STATUS_COMPLETED
self.admin_marked = administratively_marked
if success && lesson_booking.requires_teacher_distribution?(self)
if lesson_booking.posa_card.nil? && lesson_booking.payment
# if there is a payment object, it will describe how everything gets doled out
payment = JSON.parse(lesson_booking.payment)
teacher_split = payment["teacher"]
retailer_split = payment["retailer"]
retailer_rate = teacher.teacher.retailer.jamkazam_rate + APP_CONFIG.stripe[:charge_fee] # add 0.03 to account for stripe deduction
# but we must also take the correct rate out of the
if is_test_drive?
# test drives do not get subject to split behavior, per quick convo with David
self.teacher_distributions << TeacherDistribution.create_for_lesson(self, false)
else
if teacher_split && teacher_split > 0
teacher_dist = TeacherDistribution.create_for_lesson(self, false, (teacher_split / 100.0).round(2), retailer_rate)
self.teacher_distributions << teacher_dist
end
if retailer_split && retailer_split > 0
teacher_dist = TeacherDistribution.create_for_lesson(self, false, (retailer_split / 100.0).round(2), retailer_rate)
teacher_dist.retailer = teacher.teacher.retailer
teacher_dist.save
end
end
else
is_education_school_on_school = lesson_booking.school_on_school_payment?
self.teacher_distributions << TeacherDistribution.create_for_lesson(self, false)
if is_education_school_on_school
self.teacher_distributions << TeacherDistribution.create_for_lesson(self, true)
end
end
end
if self.save
# send out emails appropriate for this type of session
session_completed
end
end
def analyse
if self.analysed
return
@ -210,23 +267,8 @@ module JamRuby
# extra protection against bad code somewhere
return
end
self.success = analysis[:bill]
self.analysed_at = Time.now
self.analysed = true
self.status = STATUS_COMPLETED
if success && lesson_booking.requires_teacher_distribution?(self)
self.teacher_distributions << TeacherDistribution.create_for_lesson(self, false)
if lesson_booking.school_on_school_payment?
self.teacher_distributions << TeacherDistribution.create_for_lesson(self, true)
end
end
if self.save
# send out emails appropriate for this type of session
session_completed
end
mark_lesson(analysis[:bill])
end
def billed

View File

@ -948,6 +948,9 @@ SQL
if scheduled_duration.class == String
duration = scheduled_duration.to_i.seconds
end
if duration == 0
duration = 30 * 60
end
duration
end
@ -996,7 +999,7 @@ SQL
"#{start_time.strftime("%A, %B %e")}, #{start_time.strftime("%l:%M").strip}-#{end_time.strftime("%l:%M %p").strip} #{timezone_display}"
end
else
"#{start_time.strftime("%A, %B %e")} - #{start_time.strftime("%l:%M%P").strip}"
"#{start_time.strftime("%A, %B %e")} - #{end_time.strftime("%l:%M%P").strip}"
end
else

View File

@ -36,6 +36,10 @@ module JamRuby
sale_display
end
def variant_price(variant = nil)
price
end
def price
if card_type == JAM_TRACKS_5
10.00
@ -49,7 +53,7 @@ module JamRuby
end
def sale_display
def sale_display(variant = nil)
if card_type == JAM_TRACKS_5
'JamTracks Card (5)'
elsif card_type == JAM_TRACKS_10

View File

@ -272,7 +272,9 @@ module JamRuby
music_session.connections.each do |connection|
connection.tracks.each do |track|
recording.recorded_tracks << RecordedTrack.create_from_track(track, recording)
if connection.client_role != 'child'
recording.recorded_tracks << RecordedTrack.create_from_track(track, recording)
end
end
connection.video_sources.each do |video|

View File

@ -16,6 +16,7 @@ module JamRuby
has_many :teacher_distributions, class_name: 'JamRuby::TeacherDistribution'
has_many :sales, class_name: 'JamRuby::Sale'
has_many :sale_line_items, class_name: 'JamRuby::SaleLineItem'
has_many :lesson_bookings, class_name: 'JamRuby::LessonBooking'
validates :user, presence: true
#validates :slug, presence: true
@ -60,11 +61,23 @@ module JamRuby
BCrypt::Password.new(self.encrypted_password) == password
end
def update_payment(split)
if split[:teacher] && split[:teacher].is_a?(Integer) && split[:retailer] && split[:retailer].is_a?(Integer)
teacher_split = split[:teacher]
retailer_split = split[:retailer]
if (teacher_split >= 0 && teacher_split <= 100) && (retailer_split >= 0 && retailer_split <= 100) && (teacher_split + retailer_split == 100)
self.payment = split.to_json
end
end
end
def update_from_params(params)
self.name = params[:name] if params[:name].present?
self.city = params[:city]
self.state = params[:state]
self.slug = params[:slug] if params[:slug].present?
if params[:split]
self.update_payment(params[:split])
end
if params[:password].present?
self.should_validate_password = true
@ -74,6 +87,15 @@ module JamRuby
self.save
end
# should be of form {teacher: 0-100, retailer: 0-100}
def payment_details
if self.payment
JSON.parse(self.payment)
else
{"teacher" =>100, "retailer" => 0}
end
end
def owner
user
end

View File

@ -9,6 +9,7 @@ module JamRuby
SOURCE_RECURLY = 'recurly'
SOURCE_IOS = 'ios'
SOURCE_PAYPAL = 'paypal'
belongs_to :retailer, class_name: 'JamRuby::Retailer'
belongs_to :user, class_name: 'JamRuby::User'
@ -117,8 +118,24 @@ module JamRuby
price_info
end
def self.ios_purchase(current_user, jam_track, receipt, price_data)
jam_track_right = nil
def self.ios_purchase(current_user, jam_track, receipt, price_data, variant)
if variant.nil?
variant = ShoppingCart::JAMTRACK_STREAM
end
# see if we should bail because we already own these rights
jam_track_right = jam_track.right_for_user(current_user)
if !jam_track_right.nil? && jam_track_right.can_download
# if the user already has full rights to the JamTrack, there is nothing else to do in this path
return jam_track_right
end
if !jam_track_right.nil? && (!jam_track_right.can_download && variant == ShoppingCart::JAMTRACK_STREAM)
# if the user does have the track, but isn't upgrading it, bail
return jam_track_right
end
# everything needs to go into a transaction! If anything goes wrong, we need to raise an exception to break it
Sale.transaction do
@ -157,6 +174,11 @@ module JamRuby
jam_track_right.redeemed = using_free_credit
jam_track_right.version = jam_track.version
end
if variant == ShoppingCart::JAMTRACK_DOWNLOAD || variant == ShoppingCart::JAMTRACK_FULL
jam_track_right.can_download = true
jam_track_right.save
end
end
jam_track_right
@ -166,7 +188,7 @@ module JamRuby
# individual subscriptions will end up create their own sale (you can't have N subscriptions in one sale--recurly limitation)
# jamtracks however can be piled onto the same sale as adjustments (VRFS-3028)
# so this method may create 1 or more sales, , where 2 or more sales can occur if there are more than one subscriptions or subscription + jamtrack
def self.place_order(current_user, shopping_carts)
def self.place_order(current_user, shopping_carts, paypal = false)
sales = []
@ -176,7 +198,7 @@ module JamRuby
# return sales
#end
jam_track_sale = order_jam_tracks(current_user, shopping_carts)
jam_track_sale = order_jam_tracks(current_user, shopping_carts, paypal)
sales << jam_track_sale if jam_track_sale
# TODO: process shopping_carts_subscriptions
@ -377,7 +399,7 @@ module JamRuby
# this method will either return a valid sale, or throw a RecurlyClientError or ActiveRecord validation error (save! failed)
# it may return an nil sale if the JamTrack(s) specified by the shopping carts are already owned
def self.order_jam_tracks(current_user, shopping_carts)
def self.order_jam_tracks(current_user, shopping_carts, is_paypal)
shopping_carts_jam_tracks = []
shopping_carts_subscriptions = []
@ -395,11 +417,9 @@ module JamRuby
end
end
client = RecurlyClient.new
sale = nil
Sale.transaction do
sale = create_jam_track_sale(current_user, SOURCE_RECURLY)
sale = create_jam_track_sale(current_user, is_paypal ? SOURCE_PAYPAL : SOURCE_RECURLY)
if sale.valid?
if is_only_freebie(shopping_carts)
@ -429,61 +449,142 @@ module JamRuby
else
account = client.get_account(current_user)
if account.present?
if is_paypal
purge_pending_adjustments(account)
sale.process_shopping_carts(current_user, shopping_carts)
created_adjustments = sale.process_shopping_carts(current_user, shopping_carts, account)
paypal_auth = current_user.paypal_auth
# now invoice the sale ... almost done
@api = PayPal::SDK::Merchant::API.new
@get_express_checkout_details = @api.build_get_express_checkout_details({:Token => paypal_auth.token})
@response = @api.get_express_checkout_details(@get_express_checkout_details)
begin
invoice = account.invoice!
sale.recurly_invoice_id = invoice.uuid
sale.recurly_invoice_number = invoice.invoice_number
@@log.info("User #{current_user.email}, GetExpressCheckout: #{@response.inspect}")
tax = false
if @response.Ack == 'Success'
payerInfo = @response.GetExpressCheckoutDetailsResponseDetails.PayerInfo
if payerInfo.Address && ( payerInfo.Address.Country == 'US' && payerInfo.Address.StateOrProvince == 'TX')
# we need to ask for taxes
tax = true
end
end
tax_rate = tax ? 0.0825 : 0
total = current_user.shopping_cart_total.round(2)
tax_total = (total * tax_rate).round(2)
total = total + tax_total
total = total.round(2)
@do_express_checkout_payment = @api.build_do_express_checkout_payment({
:DoExpressCheckoutPaymentRequestDetails => {
:PaymentDetails =>
[
{
:OrderTotal => {
:currencyID => "USD",
:value => total
},
:PaymentAction => "Sale"
}
],
:Token => paypal_auth.token,
:PayerID => paypal_auth.uid, }})
@pay_response = @api.do_express_checkout_payment(@do_express_checkout_payment)
@@log.info("User #{current_user.email}, DoExpressCheckoutPayment: #{@pay_response.inspect}")
# #<PayPal::SDK::Merchant::DataTypes::DoExpressCheckoutPaymentResponseType:0x007fe511dd9b88
# @Timestamp=Sun, 11 Dec 2016 02:09:31 +0000, @Ack="Success",
# @CorrelationID="b28faf6bd90d9", @Version="117.0", @Build="000000",
# @DoExpressCheckoutPaymentResponseDetails=#<PayPal::SDK::Merchant::DataTypes::DoExpressCheckoutPaymentResponseDetailsType:0x007fe511dd38c8
# @Token="EC-7A4606566T700564B",
# @PaymentInfo=[#<PayPal::SDK::Merchant::DataTypes::PaymentInfoType:0x007fe511dd3008 @TransactionID="63C410710F2619403", @ParentTransactionID=nil,
# @ReceiptID=nil, @TransactionType="express-checkout", @PaymentType="instant", @PaymentDate=Sun, 11 Dec 2016 02:09:31 +0000,
# @GrossAmount=#<PayPal::SDK::Merchant::DataTypes::BasicAmountType:0x007fe511dd0c90 @currencyID="USD", @value="1.99">,
# @FeeAmount=#<PayPal::SDK::Merchant::DataTypes::BasicAmountType:0x007fe511dd04e8 @currencyID="USD", @value="0.36">,
# @TaxAmount=#<PayPal::SDK::Merchant::DataTypes::BasicAmountType:0x007fe511dcbe98 @currencyID="USD", @value="0.00">,
# @ExchangeRate=nil, @PaymentStatus="Completed", @PendingReason="none", @ReasonCode="none", @ProtectionEligibility="Eligible",
# @ProtectionEligibilityType="ItemNotReceivedEligible,UnauthorizedPaymentEligible", @SellerDetails=#<PayPal::SDK::Merchant::DataTypes::SellerDetailsType:0x007fe511dcb358 @SecureMerchantAccountID="6MB486RSBRMJ2">>],
# @SuccessPageRedirectRequested="false", @CoupledPaymentInfo=[#<PayPal::SDK::Merchant::DataTypes::CoupledPaymentInfoType:0x007fe511dca7a0>]>>
if @pay_response.Ack == 'Success'
details = @pay_response.DoExpressCheckoutPaymentResponseDetails.PaymentInfo[0]
sale.recurly_invoice_id = details.TransactionID
sale.recurly_invoice_number = details.ReceiptID
# now slap in all the real tax/purchase totals
sale.recurly_subtotal_in_cents = invoice.subtotal_in_cents
sale.recurly_tax_in_cents = invoice.tax_in_cents
sale.recurly_total_in_cents = invoice.total_in_cents
sale.recurly_currency = invoice.currency
# and resolve against sale_line_items
sale.sale_line_items.each do |sale_line_item|
found_line_item = false
invoice.line_items.each do |line_item|
if line_item.uuid == sale_line_item.recurly_adjustment_uuid
sale_line_item.recurly_tax_in_cents = line_item.tax_in_cents
sale_line_item.recurly_total_in_cents =line_item.total_in_cents
sale_line_item.recurly_currency = line_item.currency
sale_line_item.recurly_discount_in_cents = line_item.discount_in_cents
found_line_item = true
break
end
end
if !found_line_item
@@log.error("can't find line item #{sale_line_item.recurly_adjustment_uuid}")
puts "CANT FIND LINE ITEM"
end
end
sale.recurly_subtotal_in_cents = ((details.GrossAmount.value.to_f - details.TaxAmount.value.to_f) * 100).to_i
sale.recurly_tax_in_cents = (details.TaxAmount.value.to_f * 100).to_i
sale.recurly_total_in_cents = (details.GrossAmount.value.to_f * 100).to_i
sale.recurly_currency = details.GrossAmount.currencyID
unless sale.save
puts "WTF"
raise RecurlyClientError, "Invalid sale (at end)."
puts "Invalid sale (at end)."
raise PayPalClientError, "Invalid sale (at end)."
end
rescue Recurly::Resource::Invalid => e
# this exception is thrown by invoice! if the invoice is invalid
sale.rollback_adjustments(current_user, created_adjustments)
sale = nil
raise ActiveRecord::Rollback # kill all db activity, but don't break outside logic
rescue => e
puts "UNKNOWN E #{e}"
else
@@log.error("User #{current_user.email}, DoExpressCheckoutPayment: #{@pay_response.inspect}")
raise PayPalClientError, @pay_response.Errors[0].LongMessage
end
else
raise RecurlyClientError, "Could not find account to place order."
client = RecurlyClient.new
account = client.get_account(current_user)
if account.present?
purge_pending_adjustments(account)
created_adjustments = sale.process_shopping_carts(current_user, shopping_carts, account)
# now invoice the sale ... almost done
begin
invoice = account.invoice!
sale.recurly_invoice_id = invoice.uuid
sale.recurly_invoice_number = invoice.invoice_number
# now slap in all the real tax/purchase totals
sale.recurly_subtotal_in_cents = invoice.subtotal_in_cents
sale.recurly_tax_in_cents = invoice.tax_in_cents
sale.recurly_total_in_cents = invoice.total_in_cents
sale.recurly_currency = invoice.currency
# and resolve against sale_line_items
sale.sale_line_items.each do |sale_line_item|
found_line_item = false
invoice.line_items.each do |line_item|
if line_item.uuid == sale_line_item.recurly_adjustment_uuid
sale_line_item.recurly_tax_in_cents = line_item.tax_in_cents
sale_line_item.recurly_total_in_cents =line_item.total_in_cents
sale_line_item.recurly_currency = line_item.currency
sale_line_item.recurly_discount_in_cents = line_item.discount_in_cents
found_line_item = true
break
end
end
if !found_line_item
@@log.error("can't find line item #{sale_line_item.recurly_adjustment_uuid}")
puts "CANT FIND LINE ITEM"
end
end
unless sale.save
puts "Invalid sale (at end)."
raise RecurlyClientError, "Invalid sale (at end)."
end
rescue Recurly::Resource::Invalid => e
# this exception is thrown by invoice! if the invoice is invalid
sale.rollback_adjustments(current_user, created_adjustments)
sale = nil
raise ActiveRecord::Rollback # kill all db activity, but don't break outside logic
rescue => e
puts "UNKNOWN E #{e}"
end
else
raise RecurlyClientError, "Could not find account to place order."
end
end
end
else
@ -493,7 +594,7 @@ module JamRuby
sale
end
def process_shopping_carts(current_user, shopping_carts, account)
def process_shopping_carts(current_user, shopping_carts, account = nil)
created_adjustments = []
@ -515,7 +616,7 @@ module JamRuby
end
def process_shopping_cart(current_user, shopping_cart, account, created_adjustments)
def process_shopping_cart(current_user, shopping_cart, recurly_account, created_adjustments)
recurly_adjustment_uuid = nil
recurly_adjustment_credit_uuid = nil
@ -527,7 +628,8 @@ module JamRuby
if shopping_cart.is_jam_track?
jam_track = cart_product
if jam_track.right_for_user(current_user)
if jam_track.right_for_user(current_user, shopping_cart.variant)
# if the user already owns the JamTrack, we should just skip this cart item, and destroy it
# if this occurs, we have to reload every shopping_cart as we iterate. so, we do at the top of the loop
ShoppingCart.remove_jam_track_from_cart(current_user, shopping_cart)
@ -536,14 +638,14 @@ module JamRuby
end
if account
if recurly_account
# ask the shopping cart to create the correct Recurly adjustment attributes for a JamTrack
adjustments = shopping_cart.create_adjustment_attributes(current_user)
adjustments.each do |adjustment|
# create the adjustment at Recurly (this may not look like it, but it is a REST API)
created_adjustment = account.adjustments.new(adjustment)
created_adjustment = recurly_account.adjustments.new(adjustment)
created_adjustment.save
# if the adjustment could not be made, bail
@ -583,6 +685,13 @@ module JamRuby
jam_track_right.version = jam_track.version
end
# deal with variant behavior
if shopping_cart.purchasing_downloadable_rights?
jam_track_right.can_download = true
jam_track_right.save
end
# also if the purchase was a free one, then:
# first, mark the free has_redeemable_jamtrack field if that's still true
# and if still they have more free things, then redeem the giftable_jamtracks

View File

@ -175,6 +175,7 @@ module JamRuby
sale_line_item = SaleLineItem.new
sale_line_item.product_type = shopping_cart.cart_type
sale_line_item.variant = shopping_cart.variant
sale_line_item.unit_price = product_info[:price]
sale_line_item.quantity = product_info[:quantity]
sale_line_item.free = product_info[:marked_for_redeem]

View File

@ -10,6 +10,11 @@ module JamRuby
PURCHASE_REASONS = [PURCHASE_NORMAL, PURCHASE_FREE, PURCHASE_FREE_CREDIT]
JAMTRACK_FULL = 'full'
JAMTRACK_STREAM = 'stream'
JAMTRACK_DOWNLOAD = 'download'
JAMTRACK_VARIANTS = ['full', 'stream', 'download']
attr_accessible :quantity, :cart_type, :product_info
attr_accessor :skip_mix_check
@ -22,13 +27,15 @@ module JamRuby
validates :cart_type, presence: true
validates :cart_class_name, presence: true
validates :marked_for_redeem, numericality: {only_integer: true}
validates :variant, inclusion: {in: [nil, JAMTRACK_FULL, JAMTRACK_STREAM, JAMTRACK_DOWNLOAD]}
#validate :not_mixed
default_scope { order('created_at DESC') }
def product_info(instance = nil)
product = self.cart_product
data = {type: cart_type, name: product.name, price: product.price, product_id: cart_id, plan_code: product.plan_code, real_price: real_price(product), total_price: total_price(product), quantity: quantity, marked_for_redeem: marked_for_redeem, free: free?, sales_region: product.sales_region, sale_display:product.sale_display, allow_free: allow_free(product)} unless product.nil?
data = {type: cart_type, name: product.name, price: product.variant_price(variant), product_id: cart_id, plan_code: product.plan_code, real_price: real_price(product), total_price: total_price(product), quantity: quantity, marked_for_redeem: marked_for_redeem, free: free?, sales_region: product.sales_region, sale_display:product.sale_display(variant), allow_free: allow_free(product)} unless product.nil?
if data && instance
data.merge!(instance.product_info)
end
@ -37,12 +44,16 @@ module JamRuby
# multiply quantity by price
def total_price(product)
quantity * product.price
quantity * product.variant_price(variant)
end
def purchasing_downloadable_rights?
is_jam_track? && (variant == ShoppingCart::JAMTRACK_DOWNLOAD || variant == ShoppingCart::JAMTRACK_FULL)
end
# multiply (quantity - redeemable) by price
def real_price(product)
(quantity - marked_for_redeem) * product.price
(quantity - marked_for_redeem) * product.variant_price(variant)
end
def allow_free(product)
@ -101,7 +112,7 @@ module JamRuby
end
end
def self.create user, product, quantity = 1, mark_redeem = false
def self.create(user, product, quantity = 1, mark_redeem = false, variant = nil)
cart = ShoppingCart.new
if user.is_a?(User)
@ -111,6 +122,14 @@ module JamRuby
end
cart.cart_type = product.class::PRODUCT_TYPE
if cart.cart_type == JamTrack::PRODUCT_TYPE && variant.nil?
cart.variant = JAMTRACK_STREAM # default to jamtrack 'stream'
else
cart.variant = variant
end
cart.cart_class_name = product.class.name
cart.cart_id = product.id
cart.quantity = quantity
@ -158,9 +177,9 @@ module JamRuby
shopping_carts.each do |shopping_cart|
if shopping_cart.is_jam_track?
mark_redeem = ShoppingCart.user_has_redeemable_jam_track?(user)
cart = ShoppingCart.create(user, shopping_cart.cart_product, shopping_cart.quantity, mark_redeem)
cart = ShoppingCart.create(user, shopping_cart.cart_product, shopping_cart.quantity, mark_redeem, shopping_cart.variant)
else
cart = ShoppingCart.create(user, shopping_cart.cart_product, shopping_cart.quantity, false)
cart = ShoppingCart.create(user, shopping_cart.cart_product, shopping_cart.quantity, false, shopping_cart.variant)
end
end
@ -201,8 +220,13 @@ module JamRuby
end
# adds a jam_track to cart, checking for promotions
def self.add_jam_track_to_cart(any_user, jam_track, clear:false)
def self.add_jam_track_to_cart(any_user, jam_track, variant = JAMTRACK_FULL)
cart = nil
if variant.nil?
variant = JAMTRACK_FULL
end
ShoppingCart.transaction do
# if clear
@ -213,7 +237,7 @@ module JamRuby
end
mark_redeem = jam_track.allow_free ? ShoppingCart.user_has_redeemable_jam_track?(any_user) : false
cart = ShoppingCart.create(any_user, jam_track, 1, mark_redeem)
cart = ShoppingCart.create(any_user, jam_track, 1, mark_redeem, variant)
end
any_user.reload
cart

View File

@ -43,26 +43,35 @@ module JamRuby
end
end
def self.create_for_lesson(lesson_session, for_education)
distribution = create(lesson_session, for_education)
def self.create_for_lesson(lesson_session, for_education, split = nil, fee_rate = nil)
distribution = create(lesson_session, for_education, split, fee_rate)
distribution.lesson_session = lesson_session
distribution.education = for_education
# lock down the teacher_fee_in_cents
distribution.teacher_fee_in_cents = distribution.calculate_teacher_fee(split, fee_rate)
distribution
end
def self.create_for_lesson_package_purchase(lesson_package_purchase, for_education)
distribution = create(lesson_package_purchase, for_education)
def self.create_for_lesson_package_purchase(lesson_package_purchase, for_education, split = nil, fee_rate = nil)
distribution = create(lesson_package_purchase, for_education, split, fee_rate)
distribution.lesson_package_purchase = lesson_package_purchase
distribution.education = for_education
# lock down the teacher_fee_in_cents
distribution.teacher_fee_in_cents = distribution.calculate_teacher_fee(split, fee_rate)
distribution
end
def self.create(target, education)
def self.create(target, education, split, fee_rate)
distribution = TeacherDistribution.new
distribution.teacher = target.teacher
distribution.ready = false
distribution.distributed = false
distribution.amount_in_cents = target.lesson_booking.distribution_price_in_cents(target, education)
distribution.amount_in_cents = target.lesson_booking.distribution_price_in_cents(target, education, split, fee_rate)
distribution.school = target.lesson_booking.school
distribution
end
@ -111,23 +120,34 @@ module JamRuby
(jamkazam_margin_in_cents / 100).round(2)
end
def calculate_teacher_fee
if education
0
def calculate_teacher_fee(split = nil, fee_rate = nil)
if teacher_fee_in_cents
return teacher_fee_in_cents
else
if is_test_drive?
if education
0
else
if school
# if school exists, use it's rate
rate = school.jamkazam_rate
if is_test_drive?
0
else
# otherwise use the teacher's rate
rate = teacher.teacher.jamkazam_rate
if fee_rate
rate = (fee_rate * split) # charge_Fee is already handled elsewhere
else
if school
# if school exists, use it's rate
rate = school.jamkazam_rate + APP_CONFIG.stripe[:charge_fee]
else
# otherwise use the teacher's rate
rate = teacher.teacher.jamkazam_rate + APP_CONFIG.stripe[:charge_fee]
end
end
(amount_in_cents * rate).round
end
(amount_in_cents * (rate + 0.03)).round # 0.03 is stripe fee that we include in cost of JK fee
end
end
end
def student

View File

@ -93,7 +93,7 @@ module JamRuby
payment.amount_in_cents = payment.teacher_distribution.amount_in_cents
payment.fee_in_cents = payment.teacher_distribution.calculate_teacher_fee
effective_in_cents = payment.amount_in_cents - payment.fee_in_cents
effective_in_cents = payment.real_distribution_in_cents
if payment.teacher_payment_charge.nil?
charge = TeacherPaymentCharge.new

View File

@ -41,7 +41,8 @@ module JamRuby
:currency => "usd",
:customer => APP_CONFIG.stripe[:source_customer],
:description => construct_description,
:metadata => metadata
:metadata => metadata,
:destination => teacher.teacher.stripe_account_id
)
stripe_charge

View File

@ -1912,6 +1912,13 @@ module JamRuby
stats
end
def shopping_cart_total
total = 0
shopping_carts.each do |shopping_cart|
total += shopping_cart.product_info[:total_price]
end
total
end
def destroy_all_shopping_carts
ShoppingCart.where("user_id=?", self).destroy_all
end
@ -2053,6 +2060,15 @@ module JamRuby
user_authorizations.where(provider: "stripe_connect").first
end
def paypal_auth
user_authorizations.where(provider: 'paypal').first
end
def has_paypal_auth?
auth = paypal_auth
auth && (!auth.token_expiration || auth.token_expiration > Time.now)
end
def has_stripe_connect?
auth = stripe_auth
auth && (!auth.token_expiration || auth.token_expiration > Time.now)

View File

@ -784,6 +784,7 @@ FactoryGirl.define do
sequence(:publisher) { |n| "publisher-#{n}" }
sales_region 'United States'
price 1.99
download_price 4.99
reproduction_royalty true
public_performance_royalty true
reproduction_royalty_amount 0.999

View File

@ -24,7 +24,7 @@ describe "Monthly Recurring Lesson Flow" do
teacher.stripe_account_id = stripe_account1_id
teacher.save!
}
it "works" do
it "works normal" do
# if it's later in the month, we'll make 2 lesson_package_purchases (prorated one, and next month's), which can throw off some assertions later on
Timecop.travel(Date.new(2016, 3, 20))
@ -173,7 +173,7 @@ describe "Monthly Recurring Lesson Flow" do
booked_price = booking.booked_price
prorated = booked_price / 2
prorated_cents = (booked_price * 100).to_i
prorated_cents = (prorated * 100).to_i
user.reload
user.lesson_purchases.length.should eql 1
lesson_purchase = user.lesson_purchases[0]
@ -205,10 +205,10 @@ describe "Monthly Recurring Lesson Flow" do
teacher_distribution.distributed.should be_true
TeacherPayment.count.should eql 1
payment = TeacherPayment.first
payment.amount_in_cents.should eql 3000
payment.fee_in_cents.should eql (3000 * 0.28).round
payment.teacher_payment_charge.amount_in_cents.should eql (3000 + 3000 * APP_CONFIG.stripe[:ach_pct]).round
payment.teacher_payment_charge.fee_in_cents.should eql (3000 * 0.28).round
payment.amount_in_cents.should eql prorated_cents
payment.fee_in_cents.should eql (prorated_cents * 0.28).round
payment.teacher_payment_charge.amount_in_cents.should eql (payment.real_distribution_in_cents + payment.real_distribution_in_cents * APP_CONFIG.stripe[:ach_pct]).round
payment.teacher_payment_charge.fee_in_cents.should eql (prorated_cents * 0.28).round
payment.teacher.should eql teacher_user
payment.teacher_distribution.should eql teacher_distribution
@ -405,7 +405,7 @@ describe "Monthly Recurring Lesson Flow" do
booked_price = booking.booked_price
prorated = booked_price / 2
prorated_cents = (booked_price * 100).to_i
prorated_cents = (prorated * 100).to_i
user.reload
user.lesson_purchases.length.should eql 1
lesson_purchase = user.lesson_purchases[0]
@ -441,16 +441,16 @@ describe "Monthly Recurring Lesson Flow" do
teacher_distribution.distributed.should be_true
TeacherPayment.count.should eql 2
payment = teacher_distribution.teacher_payment
payment.amount_in_cents.should eql 3000
payment.fee_in_cents.should eql (3000 * 0.28).round
payment.teacher_payment_charge.amount_in_cents.should eql (3000 + 3000 * APP_CONFIG.stripe[:ach_pct]).round
payment.teacher_payment_charge.fee_in_cents.should eql (3000 * 0.28).round
payment.amount_in_cents.should eql prorated_cents
payment.fee_in_cents.should eql (prorated_cents * 0.28).round
payment.teacher_payment_charge.amount_in_cents.should eql (payment.real_distribution_in_cents + payment.real_distribution_in_cents * APP_CONFIG.stripe[:ach_pct]).round
payment.teacher_payment_charge.fee_in_cents.should eql (prorated_cents * 0.28).round
payment.teacher.should eql teacher_user
payment.teacher_distribution.should eql teacher_distribution
education_distribution.reload
education_distribution.distributed.should be_true
education_amt = (3000 * 0.0625).round
education_amt = (prorated_cents * 0.0625).round
payment = education_distribution.teacher_payment
payment.amount_in_cents.should eql education_amt
payment.fee_in_cents.should eql 0

View File

@ -15,6 +15,7 @@ describe "Normal Lesson Flow" do
let(:affiliate_partner) { FactoryGirl.create(:affiliate_partner) }
let(:affiliate_partner2) { FactoryGirl.create(:affiliate_partner, lesson_rate: 0.30) }
let(:school) {FactoryGirl.create(:school)}
let(:retailer) {FactoryGirl.create(:retailer)}
after {Timecop.return}
@ -459,6 +460,7 @@ describe "Normal Lesson Flow" do
booking.card_presumed_ok.should be_false
booking.user.should eql user
user.unprocessed_normal_lesson.should be_nil
booking.same_school_free.should be_true
booking.sent_notices.should be_true
booking.booked_price.should eql 30.00
booking.is_requested?.should be_true
@ -609,8 +611,8 @@ describe "Normal Lesson Flow" do
booking.school.should be_true
booking.card_presumed_ok.should be_false
booking.user.should eql user
booking.same_school_free.should be_true
user.unprocessed_normal_lesson.should be_nil
booking.same_school_free.should be_false
#user.unprocessed_normal_lesson.should be_nil
booking.sent_notices.should be_false
booking.booked_price.should eql 30.00
booking.is_requested?.should be_true
@ -793,15 +795,236 @@ describe "Normal Lesson Flow" do
payment = teacher_distribution.teacher_payment
payment.amount_in_cents.should eql 3000
payment.fee_in_cents.should eql (3000 * 0.28).round
payment.teacher_payment_charge.amount_in_cents.should eql (3000 + 3000 * APP_CONFIG.stripe[:ach_pct]).round
payment.teacher_payment_charge.fee_in_cents.should eql (3000 * 0.28).round
payment.teacher_payment_charge.amount_in_cents.should eql ((teacher_distribution.amount_in_cents - teacher_distribution.teacher_fee_in_cents) + (teacher_distribution.amount_in_cents - teacher_distribution.teacher_fee_in_cents) * APP_CONFIG.stripe[:ach_pct]).round
payment.teacher_payment_charge.fee_in_cents.should eql payment.teacher_payment_charge.fee_in_cents
payment.teacher.should eql teacher_user
payment.teacher_distribution.should eql teacher_distribution
lesson_session.lesson_booking.status.should eql LessonBooking::STATUS_COMPLETED
lesson_session.lesson_booking.success.should be_true
end
it "works (retailer on retailer)" do
# make sure teacher can get payments
teacher.stripe_account_id = stripe_account1_id
retailer.user.stripe_account_id = stripe_account2_id
# make sure can get stripe payments
# get user and teacher into same retailer
teacher_split = 70
retailer_split = 30
teacher_split_pct = (teacher_split / 100.0)
retailer_split_pct = (retailer_split / 100.0)
retailer.update_payment({teacher: teacher_split, retailer:retailer_split})
retailer.save!
user.affiliate_referral = retailer.affiliate_partner
user.save!
teacher.retailer = retailer
teacher.save!
# user has no test drives, no credit card on file, but attempts to book a lesson
booking = LessonBooking.book_normal(user, teacher_user, valid_single_slots, "Hey I've heard of you before.", false, LessonBooking::PAYMENT_STYLE_SINGLE, 60)
booking.errors.any?.should be_false
booking.retailer.should eql retailer
booking.card_presumed_ok.should be_false
booking.user.should eql user
booking.same_school_free.should be_false
booking.same_retailer.should be_true
#user.unprocessed_normal_lesson.should be_nil
booking.sent_notices.should be_false
booking.booked_price.should eql 30.00
booking.is_requested?.should be_true
booking.lesson_sessions[0].music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
LessonPaymentCharge.count.should eql 1
########## Need validate their credit card
token = create_stripe_token
result = user.payment_update({token: token, zip: '78759', normal: true, booking_id: booking.id})
booking = result[:lesson]
lesson = booking.lesson_sessions[0]
booking.errors.any?.should be_false
lesson.errors.any?.should be_false
booking.card_presumed_ok.should be_true
booking.sent_notices.should be_true
lesson.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
lesson.amount_charged.should eql 0.0
lesson.reload
user.reload
user.stripe_customer_id.should_not be nil
user.remaining_test_drives.should eql 0
user.lesson_purchases.length.should eql 0
customer = Stripe::Customer.retrieve(user.stripe_customer_id)
customer.email.should eql user.email
booking.lesson_sessions.length.should eql 1
lesson_session = booking.lesson_sessions[0]
lesson_session.status.should eql LessonBooking::STATUS_REQUESTED
booking.status.should eql LessonBooking::STATUS_REQUESTED
######### Teacher counters with new slot
teacher_countered_slot = FactoryGirl.build(:lesson_booking_slot_single, hour: 14)
UserMailer.deliveries.clear
lesson_session.counter({proposer: teacher_user, slot: teacher_countered_slot, message: 'Does this work?'})
booking.reload
booking.errors.any?.should be false
lesson_session.lesson_booking.errors.any?.should be false
lesson_session.lesson_booking_slots.length.should eql 1
lesson_session.lesson_booking_slots[0].proposer.should eql teacher_user
teacher_counter = lesson_session.lesson_booking_slots.order(:created_at).last
teacher_counter.should eql teacher_countered_slot
teacher_counter.proposer.should eql teacher_user
booking.lesson_booking_slots.length.should eql 3
UserMailer.deliveries.length.should eql 1
chat = ChatMessage.unscoped.order(:created_at).last
chat.channel.should eql ChatMessage::CHANNEL_LESSON
chat.message.should eql 'Does this work?'
chat.user.should eql teacher_user
chat.target_user.should eql user
notification = Notification.unscoped.order(:created_at).last
notification.session_id.should eql lesson_session.music_session.id
notification.student_directed.should eql true
notification.purpose.should eql 'counter'
notification.description.should eql NotificationTypes::LESSON_MESSAGE
######### Student counters with new slot
student_countered_slot = FactoryGirl.build(:lesson_booking_slot_single, hour: 16)
UserMailer.deliveries.clear
lesson_session.counter({proposer: user, slot: student_countered_slot, message: 'Does this work better?'})
lesson_session.errors.any?.should be false
lesson_session.lesson_booking.errors.any?.should be false
lesson_session.lesson_booking_slots.length.should eql 2
student_counter = booking.lesson_booking_slots.order(:created_at).last
student_counter.proposer.should eql user
booking.reload
booking.lesson_booking_slots.length.should eql 4
UserMailer.deliveries.length.should eql 1
chat = ChatMessage.unscoped.order(:created_at).last
chat.message.should eql 'Does this work better?'
chat.channel.should eql ChatMessage::CHANNEL_LESSON
chat.user.should eql user
chat.target_user.should eql teacher_user
notification = Notification.unscoped.order(:created_at).last
notification.session_id.should eql lesson_session.music_session.id
notification.student_directed.should eql false
notification.purpose.should eql 'counter'
notification.description.should eql NotificationTypes::LESSON_MESSAGE
######## Teacher accepts slot
UserMailer.deliveries.clear
lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, update_all: false, accepter: teacher_user})
lesson_session.errors.any?.should be_false
lesson_session.reload
lesson_session.slot.should eql student_counter
lesson_session.status.should eql LessonSession::STATUS_APPROVED
booking.reload
booking.default_slot.should eql student_counter
lesson_session.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0)
booking.status.should eql LessonBooking::STATUS_APPROVED
UserMailer.deliveries.length.should eql 2
chat = ChatMessage.unscoped.order(:created_at).last
chat.message.should eql 'Yeah I got this'
chat.purpose.should eql 'Lesson Approved'
chat.channel.should eql ChatMessage::CHANNEL_LESSON
chat.user.should eql teacher_user
chat.target_user.should eql user
notification = Notification.unscoped.order(:created_at).last
notification.session_id.should eql lesson_session.music_session.id
notification.student_directed.should eql true
notification.purpose.should eql 'accept'
notification.description.should eql NotificationTypes::LESSON_MESSAGE
# teacher & student get into session
start = lesson_session.scheduled_start
end_time = lesson_session.scheduled_start + (60 * lesson_session.duration)
uh2 = FactoryGirl.create(:music_session_user_history, user: teacher_user, history: lesson_session.music_session, created_at: start, session_removed_at: end_time)
# artificially end the session, which is covered by other background jobs
lesson_session.music_session.session_removed_at = end_time
lesson_session.music_session.save!
Timecop.travel(end_time + 1)
UserMailer.deliveries.clear
# background code comes around and analyses the session
LessonSession.hourly_check
lesson_session.reload
lesson_session.analysed.should be_true
analysis = lesson_session.analysis
analysis["reason"].should eql LessonSessionAnalyser::STUDENT_FAULT
analysis["student"].should eql LessonSessionAnalyser::NO_SHOW
lesson_session.billed.should be_true
if lesson_session.billing_error_detail
puts "testdrive flow #{lesson_session.billing_error_detail}" # this should not occur, but helps a great deal if a regression occurs and running all the tests
end
lesson_session.billing_attempts.should eql 1
user.reload
user.lesson_purchases.length.should eql 1
LessonBooking.hourly_check
lesson_session.reload
lesson_session.education_distribution.should be_nil
teacher_distribution = lesson_session.teacher_distribution
teacher_distribution.amount_in_cents.should eql (3000 * teacher_split_pct).round
teacher_distribution.teacher_fee_in_cents.should eql (teacher_distribution.amount_in_cents * (teacher_split_pct * (retailer.jamkazam_rate + APP_CONFIG.stripe[:charge_fee]))).round
teacher_distribution.ready.should be_true
teacher_distribution.distributed.should be_false
lesson_session.teacher_distributions.count.should eql 2
retailer_distribution = lesson_session.retailer_distribution
retailer_distribution.amount_in_cents.should eql (3000 * retailer_split_pct).round
retailer_distribution.teacher_fee_in_cents.should eql (retailer_distribution.amount_in_cents * (retailer_split_pct * (retailer.jamkazam_rate + APP_CONFIG.stripe[:charge_fee]))).round
retailer_distribution.ready.should be_true
retailer_distribution.distributed.should be_false
lesson_session.billed.should be true
user.reload
user.lesson_purchases.length.should eql 1
user.sales.length.should eql 1
lesson_session.amount_charged.should eql 32.48
lesson_session.billing_error_reason.should be_nil
lesson_session.sent_billing_notices.should be_true
user.reload
user.remaining_test_drives.should eql 0
UserMailer.deliveries.length.should eql 2 # one for student, one for teacher
TeacherPayment.count.should eql 0
TeacherPayment.hourly_check
TeacherPayment.count.should eql 2
LessonPaymentCharge.count.should eql 1
TeacherDistribution.count.should eql 2
teacher_distribution.reload
teacher_distribution.distributed.should be_true
retailer_distribution.reload
retailer_distribution.distributed.should be_true
retailer_amt = (3000 * retailer_split_pct).round
payment = retailer_distribution.teacher_payment
payment.amount_in_cents.should eql retailer_distribution.amount_in_cents
payment.fee_in_cents.should eql retailer_distribution.teacher_fee_in_cents
payment.teacher_payment_charge.amount_in_cents.should eql ((retailer_distribution.amount_in_cents - retailer_distribution.teacher_fee_in_cents) + (retailer_distribution.amount_in_cents - retailer_distribution.teacher_fee_in_cents) * APP_CONFIG.stripe[:ach_pct]).round
payment.teacher_payment_charge.fee_in_cents.should eql retailer_distribution.teacher_fee_in_cents
payment.teacher.should eql teacher_user
payment.teacher_distribution.should eql retailer_distribution
payment = teacher_distribution.teacher_payment
payment.amount_in_cents.should eql teacher_distribution.amount_in_cents
payment.fee_in_cents.should eql teacher_distribution.teacher_fee_in_cents
payment.teacher_payment_charge.amount_in_cents.should eql ((teacher_distribution.amount_in_cents - teacher_distribution.teacher_fee_in_cents) + (teacher_distribution.amount_in_cents - teacher_distribution.teacher_fee_in_cents) * APP_CONFIG.stripe[:ach_pct]).round
payment.teacher_payment_charge.fee_in_cents.should eql payment.teacher_payment_charge.fee_in_cents
payment.teacher.should eql teacher_user
payment.teacher_distribution.should eql teacher_distribution
lesson_session.lesson_booking.status.should eql LessonBooking::STATUS_COMPLETED
lesson_session.lesson_booking.success.should be_true
end
it "affiliate gets their cut" do

View File

@ -338,7 +338,7 @@ describe LessonBooking do
slot.day_of_week = jan1.wday
booking = LessonBooking.book_normal(user, teacher_user, valid_recurring_slots, "Hey I've heard of you before.", true, LessonBooking::PAYMENT_STYLE_WEEKLY, 60)
times = booking.predicted_times_for_month(next_year, 1)
times = booking.predicted_times_for_month(next_year, 1)[:times]
times.length.should eql 5
times[0].to_date.should eql (jan1)
times[1].to_date.should eql (Date.new(next_year, 1, 8))
@ -357,7 +357,7 @@ describe LessonBooking do
slot.day_of_week = jan1.wday
booking = LessonBooking.book_normal(user, teacher_user, valid_recurring_slots, "Hey I've heard of you before.", true, LessonBooking::PAYMENT_STYLE_WEEKLY, 60)
times = booking.predicted_times_for_month(next_year, 1)
times = booking.predicted_times_for_month(next_year, 1)[:times]
times.length.should eql 3
times[0].to_date.should eql (Date.new(next_year, 1, 15))
times[1].to_date.should eql (Date.new(next_year, 1, 22))

View File

@ -890,29 +890,30 @@ describe MusicSession do
end
end
describe "purgeable sessions " do
describe "purgeable sessions" do
it 'selects unscheduled sessions past due date' do
interval = MusicSession::UNSTARTED_INTERVAL_DAYS_PURGE
dd = Time.now - (interval.to_i + 1).days
Timecop.travel(dd)
msess1 = FactoryGirl.create(:music_session)
dd = Time.now - (interval.to_i + 1).days
Timecop.travel(dd)
msess2 = FactoryGirl.create(:music_session)
purging = MusicSession.purgeable_sessions
expect(purging.size).to be(1)
expect(purging[0].id).to eq(msess1.id)
expect(purging[0].id).to eq(msess2.id)
end
it 'selects recurring and non-recurring sessions past due date' do
[MusicSession::UNSTARTED_INTERVAL_DAYS_PURGE,
MusicSession::UNSTARTED_INTERVAL_DAYS_PURGE_RECUR].each do |interval|
Timecop.return
msess1 = FactoryGirl.create(:music_session, scheduled_start: Time.now)
dd = Time.now - (interval.to_i + 1).days
Timecop.travel(dd)
msess1 = FactoryGirl.create(:music_session, scheduled_start: Time.now)
msess2 = FactoryGirl.create(:music_session, scheduled_start: Time.now)
purging = MusicSession.purgeable_sessions
expect(purging.size).to be(1)
expect(purging[0].id).to eq(msess1.id)
expect(purging[0].id).to eq(msess2.id)
MusicSession.delete_all
end
end

View File

@ -20,7 +20,6 @@ describe Retailer do
end
it "has correct associations" do
retailer = FactoryGirl.create(:retailer)
retailer.slug.should eql retailer.id
retailer.should eql retailer.user.owned_retailer

View File

@ -486,7 +486,7 @@ describe Sale do
purchase= adjustments[0]
purchase.unit_amount_in_cents.should eq((jamtrack.price * 100).to_i)
purchase.accounting_code.should eq(ShoppingCart::PURCHASE_NORMAL)
purchase.description.should eq("JamTrack: " + jamtrack.name)
purchase.description.should eq("JamTrack: " + jamtrack.name + '- FOR USE ONLY WITHIN APP')
purchase.state.should eq('invoiced')
purchase.uuid.should eq(sale_line_item.recurly_adjustment_uuid)

View File

@ -32,11 +32,11 @@ describe ShoppingCart do
end
it "allows mix of free and not free stuff" do
cart1 = ShoppingCart.add_jam_track_to_cart(user, jam_track, clear: true)
cart1 = ShoppingCart.add_jam_track_to_cart(user, jam_track)
cart1.should_not be_nil
cart1.errors.any?.should be_false
user.reload
cart2 = ShoppingCart.add_jam_track_to_cart(user, jam_track, clear: true)
cart2 = ShoppingCart.add_jam_track_to_cart(user, jam_track)
cart2.errors.any?.should be_false
user.reload
user.shopping_carts.length.should eq(1)
@ -44,7 +44,7 @@ describe ShoppingCart do
cart3.errors.any?.should be_false
user.reload
user.shopping_carts.length.should eq(2)
cart4 = ShoppingCart.add_jam_track_to_cart(user, jam_track2, clear: true)
cart4 = ShoppingCart.add_jam_track_to_cart(user, jam_track2)
cart4.errors.any?.should be_false
user.reload
user.shopping_carts.length.should eq(3)
@ -70,12 +70,12 @@ describe ShoppingCart do
it "removes redeemable item to shopping cart (maintains only one in cart)" do
user.has_redeemable_jamtrack.should be_true
cart1 = ShoppingCart.add_jam_track_to_cart(user, jam_track, clear: true)
cart1 = ShoppingCart.add_jam_track_to_cart(user, jam_track)
cart1.should_not be_nil
cart1.errors.any?.should be_false
cart1.marked_for_redeem.should eq(1)
user.reload
cart2 = ShoppingCart.add_jam_track_to_cart(user, jam_track2, clear: true)
cart2 = ShoppingCart.add_jam_track_to_cart(user, jam_track2)
cart2.should_not be_nil
cart2.errors.any?.should be_false
cart2.marked_for_redeem.should eq(1)

View File

@ -265,7 +265,7 @@ describe TeacherPayment do
teacher_distribution = payment.teacher_payment_charge.distribution
teacher_distribution.amount_in_cents.should eql 1000
charge = Stripe::Charge.retrieve(payment.teacher_payment_charge.stripe_charge_id)
charge.destination.should be_nil
charge.destination.should_not be_nil
charge.amount.should eql 726
charge.application_fee.should be_nil
end

View File

@ -306,7 +306,8 @@ def app_config
:publishable_key => 'pk_test_HLTvioRAxN3hr5fNfrztZeoX',
:secret_key => 'sk_test_OkjoIF7FmdjunyNsdVqJD02D',
:source_customer => 'cus_88Vp44SLnBWMXq', # seth@jamkazam.com in JamKazam-test account
:ach_pct => 0.008
:ach_pct => 0.008,
:charge_fee => 0.03
}
end
def musician_count

View File

@ -28,6 +28,8 @@ gem 'sprockets-rails', '2.3.2'
gem 'non-stupid-digest-assets'
#gem 'license_finder'
gem 'pg_migrate', '0.1.14'
#gem 'paypal-sdk-rest'
gem 'paypal-sdk-merchant-jk', '1.118.1'
gem 'kickbox'
gem 'oj', '2.10.2'
gem 'builder'
@ -70,7 +72,7 @@ gem 'filepicker-rails', '0.1.0'
gem 'aws-sdk', '~> 1'
gem 'aasm' #, '3.0.16'
gem 'carmen'
gem 'carrierwave' #, '0.9.0'
gem 'carrierwave', '0.11.2' #, '0.9.0'
gem 'carrierwave_direct'
gem 'fog'
#gem 'jquery-payment-rails', github: 'sethcall/jquery-payment-rails'
@ -163,7 +165,7 @@ end
gem 'sass-rails'
gem 'coffee-rails'
gem 'uglifier'
gem 'coffee-script-source', '1.11.1'
group :test, :cucumber do
gem 'simplecov', '~> 0.7.1'
gem 'simplecov-rcov'

View File

@ -27,7 +27,7 @@
//= require jquery.Jcrop
//= require jquery.naturalsize
//= require jquery.queryparams
//= require jquery.clipboard
//= require clipboard
//= require jquery.timeago
//= require jquery.easydropdown
//= require jquery.scrollTo

View File

@ -65,6 +65,8 @@
return;
}
console.log("GetLocalRecordingState", localResults)
$.each(claimedRecordings, function(index, claimedRecording) {
var options = {

View File

@ -10,6 +10,8 @@
var $dialog = null;
var $saveVideoCheckbox = null
var $uploadToYoutube = null
var timeout = null
var CLIENT_ROLE = context.JK.CLIENT_ROLE
function resetForm() {
// remove all display errors
@ -44,6 +46,47 @@
resetForm();
if(context.jamClient.getClientParentChildRole() == CLIENT_ROLE.CHILD) {
logger.debug("child client; launching preview after xfer");
$('#recording-finished-dialog span.nowait').addClass('hidden')
$('#recording-finished-dialog span.pleasewait').removeClass('hidden')
$('#recording-finished-dialog .preview-area').css('visibility', 'hidden')
$('#recording-finished-dialog form').css('visibility', 'hidden')
waitForMixTransfer()
}
else {
console.log("normal client; launching preview immediately")
$('#recording-finished-dialog span.pleasewait').addClass('hidden')
$('#recording-finished-dialog span.nowait').removeClass('hidden')
$('#recording-finished-dialog .preview-area').css('visibility', 'visible')
$('#recording-finished-dialog form').css('visibility', 'visible')
launchPreview();
}
}
function waitForMixTransfer() {
timeout = setTimeout(function() {
console.log("checking for file transfer", window.RecordingStore.mixTransferred)
if(window.RecordingStore.mixTransferred) {
$('#recording-finished-dialog span.pleasewait').addClass('hidden')
$('#recording-finished-dialog span.nowait').removeClass('hidden')
$('#recording-finished-dialog .preview-area').css('visibility', 'visible')
$('#recording-finished-dialog form').css('visibility', 'visible')
timeout = null
launchPreview()
}
else {
waitForMixTransfer();
}
}, 1000)
}
function launchPreview() {
var parentSelector = '#recording-finished-dialog div.genre-selector';
context.JK.GenreSelectorHelper.render(parentSelector);
@ -117,10 +160,12 @@
playbackControls.startMonitor();
}
}
}
function afterHide() {
if(timeout) {
clearTimeout(timeout)
timeout = null
}
if(recording && recording.video) {
var name = $('#recording-finished-dialog form input[name=name]').val();
name = name.replace(/[^A-Za-z0-9\-\ ]/g, '');

View File

@ -11,6 +11,7 @@
var userDetail = null;
var entity = null;
var remainingCap = 140 - 22 - 1; // 140 tweet max, minus 22 for link size, minus 1 for space
var clipboard = null;
function showSpinner() {
$(dialogId + ' .dialog-inner').hide();
@ -444,27 +445,6 @@
function afterShow() {
$("#shareType").text(entityType);
if(context.JK.hasFlash()) {
$("#btn-share-copy").clipboard({
path: '/assets/jquery.clipboard.swf',
copy: function() {
// Return text in closest element (useful when you have multiple boxes that can be copied)
return $(".link-contents").text();
}
});
}
else {
if(context.jamClient) {
// uses bridge call to ultimately access QClipboard
$("#btn-share-copy").unbind('click').click(function() {
context.jamClient.SaveToClipboard($(".link-contents").text());
return false;
})
}
else {
logger.debug("no copy-to-clipboard capabilities")
}
}
}
function afterHide() {
@ -486,6 +466,20 @@
//initDialog();
facebookHelper.deferredLoginStatus().done(function(response) { handleFbStateChange(response); });
if(context.jamClient.IsNativeClient()) {
$("#btn-share-copy").unbind('click').click(function() {
context.jamClient.SaveToClipboard($("#link-contents").text());
return false;
})
}
else {
clipboard = new Clipboard('#btn-share-copy', {
text: function(trigger) {
return $("#link-contents").text();
}
})
}
}
this.initialize = initialize;

View File

@ -188,6 +188,37 @@
return context.JK.prodBubble($element, 'teacher-profile', {}, bigHelpDarkOptions({spikeGirth:0, spikeLength: 0, duration:10000, offsetParent:$offsetParent, width:385, positions:['top', 'right', 'bottom']}))
}
helpBubble.jamtrackVariants = function($element, $offsetParent) {
var offer = function() {
console.log("jamtrackVariant turn off")
$element.btOff()
$offsetParent.off('click', offer)
}
var bubble = context.JK.prodBubble($element, 'jamtrack-variants', {}, bigHelpDarkOptions({clickAnywhereToClose: true, spikeGirth:0, spikeLength: 0, duration:20000, positions:['bottom', 'right', 'left'], offsetParent: $offsetParent}))
setTimeout(function() {
$offsetParent.on('click', offer)
}, 1)
return bubble
}
helpBubble.jamtrackUpgrade = function($element, $offsetParent) {
var offer = function() {
console.log("jamtrackUpgrade turn off")
$element.btOff()
$offsetParent.off('click', offer)
}
var bubble = context.JK.prodBubble($element, 'jamtrack-upgrade', {}, bigHelpDarkOptions({clickAnywhereToClose: true, spikeGirth:0, spikeLength: 0, duration:20000, positions:['bottom', 'right', 'left'], offsetParent: $offsetParent}))
setTimeout(function() {
$offsetParent.on('click', offer)
}, 1)
return bubble
}
helpBubble.showUseRemainingTestDrives = function($element, $offsetParent, user, callback) {
return context.JK.onceBubble($element, 'side-remaining-test-drives', user, {offsetParent:$offsetParent, width:260, positions:['right'], postShow: function(container) {

View File

@ -2734,6 +2734,27 @@
})
}
function paypalDetail(options) {
options = options || {}
return $.ajax({
type: 'POST',
url: '/api/paypal/checkout/detail',
dataType: 'json',
contentType: 'application/json',
data: JSON.stringify(options)
})
}
function paypalPlaceOrder(options) {
options = options || {}
return $.ajax({
type: 'POST',
url: '/api/paypal/checkout/confirm',
dataType: 'json',
contentType: 'application/json',
data: JSON.stringify(options)
})
}
function initialize() {
return self;
@ -2977,6 +2998,8 @@
this.posaActivate = posaActivate;
this.posaClaim = posaClaim;
this.sendRetailerCustomerEmail = sendRetailerCustomerEmail;
this.paypalDetail = paypalDetail;
this.paypalPlaceOrder = paypalPlaceOrder;
return this;
};
})(window,jQuery);

View File

@ -9,7 +9,7 @@
//= require jquery.queryparams
//= require jquery.hoverIntent
//= require jquery.cookie
//= require jquery.clipboard
//= require clipboard
//= require jquery.easydropdown
//= require jquery.carousel-1.1
//= require jquery.mousewheel-3.1.9

View File

@ -496,7 +496,10 @@
monitoring = false;
logger.debug("playbackControl.stopMonitor")
if (monitorPlaybackTimeout != null) {
clearTimeout(monitorPlaybackTimeout);
if(clearTimeout) {
clearTimeout(monitorPlaybackTimeout);
}
monitorPlaybackTimeout = null;
}
}

View File

@ -92,6 +92,7 @@ profileUtils = context.JK.ProfileUtils
selected: 'account',
updateErrors: null,
retailerName: null,
teacherSplit: null,
teacherInvitations: null,
updating: false
}
@ -121,18 +122,25 @@ profileUtils = context.JK.ProfileUtils
city = @root.find('select[name="cities"]').val()
password = @root.find('input[type="password"]').val()
teacherSplit = @teacherSplit()
if teacherSplit
retailerSplit = Number((100 - teacherSplit).toFixed(2))
@setState(updating: true)
rest.updateRetailer({
id: this.state.retailer.id,
name: name,
state: region,
city: city,
password:password
password:password,
split: {teacher: teacherSplit, retailer: retailerSplit}
}).done((response) => @onUpdateDone(response)).fail((jqXHR) => @onUpdateFail(jqXHR))
onUpdateDone: (response) ->
@setState({retailer: response, retailerName: null, updateErrors: null, updating: false})
@setState({retailer: response, retailerName: null, teacherSplit: null, updateErrors: null, updating: false})
@app.layout.notify({title: "update success", text: "Your retailer information has been successfully updated"})
@ -262,6 +270,63 @@ profileUtils = context.JK.ProfileUtils
logger.debug("handleLocationChange #{country} #{region} ${city}")
@setState({city: city, region: region})
teacherSplitCurrent: () ->
if this.state.teacherSplit?
console.log("taking state for teacher split")
this.state.teacherSplit
else
this.state.retailer.payment_details.teacher
teacherSplitValue: () ->
@teacherSplitCurrent()
retailerSplitValue: () ->
teacherSplit = @teacherSplitCurrent()
return (100 - teacherSplit).toFixed(2)
onTeacherBlur: () ->
teacherSplit = @root.find('input[name="teacher-split"]').val()
teacherSplit = Number(teacherSplit)
if teacherSplit != teacherSplit #NaN?
@setState({teacherSplit: null})
teacherSplit: () ->
teacherSplit = @root.find('input[name="teacher-split"]').val()
if teacherSplit
teacherSplit = Number(teacherSplit)
if !teacherSplit
console.log("defaulting to 100 because teachersplit is empty")
teacherSplit = 100
teacherSplit
onTeacherSplitChange: (e) ->
$target = $(e.target)
# edge cases first
teacherSplit = @root.find('input[name="teacher-split"]').val()
if teacherSplit == null || teacherSplit == ''
@setState({teacherSplit: ''})
return
teacherSplit = Number(teacherSplit)
if teacherSplit != teacherSplit # NaN?
console.log("teacher split is NaN; ignoring")
# do nothing; this way junk doesn't start showing up in retail square. Onblur will fix
return
teacherSplit = @teacherSplit()
if teacherSplit > 100
console.log("teacher split is > 100. setting to 100")
return
if teacherSplit < 0
console.log("teacher split is < 0. setting to 0")
return
@setState({teacherSplit: teacherSplit})
account: () ->
nameErrors = context.JK.reactSingleFieldErrors('name', @state.updateErrors)
@ -313,6 +378,19 @@ profileUtils = context.JK.ProfileUtils
<StripeConnect purpose='retailer' user={this.state.user}/>
</div>
<div className="field split">
<div className="teacher-split">
<label>Teacher % of Each Lesson:</label>
<input name="teacher-split" className="split-input teacher" type="number" defaultValue="" placeholder="please enter a value 0-100" value={this.teacherSplitValue()} onChange={this.onTeacherSplitChange} onBlur={this.onTeacherBlur}/>
<span className="usage-hint">Enter 0-100</span>
</div>
<div className="retailer-split">
<label>Retailer % of Each Lesson:</label>
<input name="retailer-split" className="split-input retailer" type="number" defaultValue="" value={this.retailerSplitValue()} readonly={true} disabled={true}/>
<span className="usage-hint">This is computed automatically based on the Teacher %</span>
</div>
</div>
<div className="actions">
<a className={classNames(cancelClasses)} onClick={this.onCancel}>CANCEL</a>
<a className={classNames(updateClasses)} onClick={this.onUpdate}>UPDATE</a>

View File

@ -249,7 +249,7 @@ JamBlasterActions = @JamBlasterActions
audio: () ->
`<div className="audio-content">
<JamBlasterTrackConfig />
To edit the JamBlaster audio settings, get into a session, and click the Settings link under My Tracks.
</div>`
ipSettingsChanged: (key, e) ->

View File

@ -194,13 +194,14 @@ LessonTimerActions = context.LessonTimerActions
@app.ajaxError(jqXHR)
cancelSelected: (lesson, recurring) ->
rest.checkLessonCancel({id: lesson.id, update_all: recurring}).done((response) => (@issueCancelLesson(lesson,
recurring))).fail((jqXHR) => (@cancelSelectedFail(jqXHR)))
rest.checkLessonCancel({id: lesson.id, update_all: recurring})
.done((response) => @issueCancelLesson(lesson, recurring))
.fail((jqXHR) => @cancelSelectedFail(jqXHR, lesson))
cancelSelectedFailed: (jqXHR) ->
cancelSelectedFail: (jqXHR, lesson) ->
if jqXHR.status == 422
if recurring
if lesson.recurring
if @viewerStudent()
buttons = []
buttons.push({name: 'CLOSE', buttonStyle: 'button-grey'})

View File

@ -73,13 +73,37 @@ MIX_MODES = context.JK.MIX_MODES
actionBtn = null
if jamtrack.purchased
actionBtn = `<a className="jamtrack-add-cart-disabled button-grey button-disabled" href="javascript:void(0)">PURCHASED</a>`
if jamtrack.can_download
actionBtn = `<a className="jamtrack-add-cart-disabled button-grey button-disabled" href="javascript:void(0)">PURCHASED</a>`
else
priceNotice = `<div className={jamtrackPricesClasses}>$ {Number(jamtrack.upgrade_price).toFixed(2)}</div>`
actionBtn = `<div>
<div className="jamtrack-add-zone">
{priceNotice}
<a className="jamtrack-add-cart button-orange" href="#" data-jamtrack-id={jamtrack.id} data-variant="download">UPGRADE TO FULL</a>
</div>
<a className="jamtrack-upgrade-help" href='#'>HELP</a>
</div>`
else if jamtrack.is_free
actionBtn = `<a className="jamtrack-add-cart button-orange is_free" href="#" data-jamtrack-id={jamtrack.id}>GET IT FREE!</a>`
actionBtn = `<a className="jamtrack-add-cart button-orange is_free" href="#" data-jamtrack-id={jamtrack.id} data-variant="full">GET IT FREE!</a>`
else if jamtrack.added_cart
actionBtn = `<a className="jamtrack-add-cart-disabled button-grey button-disabled" href="client#/shoppingCart">ALREADY IN CART</a>`
else
actionBtn = `<a className="jamtrack-add-cart button-orange" href="#" data-jamtrack-id={jamtrack.id}>ADD TO CART</a>`
priceNotice = `<div className={jamtrackPricesClasses}>$ {jamtrack.price}</div>`
fullPriceNotice = `<div className={jamtrackPricesClasses}>$ {jamtrack.download_price}</div>`
actionBtn = `<div>
<div className="jamtrack-add-zone">
{priceNotice}
<a className="jamtrack-add-cart button-orange" href="#" data-jamtrack-id={jamtrack.id} data-variant="stream">ADD TO CART</a>
</div>
<div className="jamtrack-add-zone">
{fullPriceNotice}
<a className="jamtrack-add-cart button-orange" href="#" data-jamtrack-id={jamtrack.id} data-variant="full">ADD TO CART (FULL)</a>
</div>
<a className="jamtrack-variant-help" href='#'>HELP</a>
</div>`
availabilityNotice = null
if jamtrack.sales_region==context.JK.AVAILABILITY_US
@ -114,7 +138,6 @@ MIX_MODES = context.JK.MIX_MODES
<td className="jamtrack-action">
<div className="jamtrack-action-container">
<div className="jamtrack-actions">
<div className={jamtrackPricesClasses}>$ {jamtrack.price}</div>
{actionBtn}
{availabilityNotice}
</div>
@ -213,7 +236,7 @@ MIX_MODES = context.JK.MIX_MODES
.done((response) =>
@setState({jamtracks: response.jamtracks, next: response.next, searching: false, first_search: false, currentPage: 1, count: response.count})
)
.fail(() =>
.fail((jqXHR) =>
@app.notifyServerError jqXHR, 'Search Unavailable'
@setState({searching: false, first_search: false})
)
@ -300,6 +323,7 @@ MIX_MODES = context.JK.MIX_MODES
e.preventDefault()
$target = $(e.target)
params = id: $target.attr('data-jamtrack-id')
params.variant = $target.attr('data-variant')
isFree = $(e.target).is('.is_free')
@rest.addJamtrackToShoppingCart(params).done((response) =>
@ -322,6 +346,18 @@ MIX_MODES = context.JK.MIX_MODES
$parent.find('.jamtrack-add-cart').on 'click', @addToCartJamtrack
$parent.find('.license-us-why').on 'click', @licenseUSWhy
$parent.find('.jamtrack-detail-btn').on 'click', @toggleExpanded
$parent.find('.jamtrack-variant-help').on 'click', @showVariantHelp
$parent.find('.jamtrack-upgrade-help').on 'click', @showUpgradeHelp
showVariantHelp: (e) ->
$screen = $('#jamtrackFilter')
e.preventDefault()
context.JK.HelpBubbleHelper.jamtrackVariants($(e.target), $screen)
showUpgradeHelp: (e) ->
$screen = $('#jamtrackFilter')
e.preventDefault()
context.JK.HelpBubbleHelper.jamtrackUpgrade($(e.target), $screen)
toggleExpanded:(e) ->
e.preventDefault()

View File

@ -95,13 +95,37 @@ MIX_MODES = context.JK.MIX_MODES
actionBtn = null
if jamtrack.purchased
actionBtn = `<a className="jamtrack-add-cart-disabled button-grey button-disabled" href="javascript:void(0)">PURCHASED</a>`
if jamtrack.can_download
actionBtn = `<a className="jamtrack-add-cart-disabled button-grey button-disabled" href="javascript:void(0)">PURCHASED</a>`
else
priceNotice = `<div className={jamtrackPricesClasses}>$ {Number(jamtrack.upgrade_price).toFixed(2)}</div>`
actionBtn = `<div>
<div className="jamtrack-add-zone">
{priceNotice}
<a className="jamtrack-add-cart button-orange" href="#" data-jamtrack-id={jamtrack.id} data-variant="download">UPGRADE TO FULL</a>
</div>
<a className="jamtrack-upgrade-help" href='#'>HELP</a>
</div>`
else if jamtrack.is_free
actionBtn = `<a className="jamtrack-add-cart button-orange is_free" href="#" data-jamtrack-id={jamtrack.id}>GET IT FREE!</a>`
actionBtn = `<a className="jamtrack-add-cart button-orange is_free" href="#" data-jamtrack-id={jamtrack.id} data-variant="full">GET IT FREE!</a>`
else if jamtrack.added_cart
actionBtn = `<a className="jamtrack-add-cart-disabled button-grey button-disabled" href="client#/shoppingCart">ALREADY IN CART</a>`
else
actionBtn = `<a className="jamtrack-add-cart button-orange" href="#" data-jamtrack-id={jamtrack.id}>ADD TO CART</a>`
priceNotice = `<div className={jamtrackPricesClasses}>$ {jamtrack.price}</div>`
fullPriceNotice = `<div className={jamtrackPricesClasses}>$ {jamtrack.download_price}</div>`
actionBtn = `<div>
<div className="jamtrack-add-zone">
{priceNotice}
<a className="jamtrack-add-cart button-orange" href="#" data-jamtrack-id={jamtrack.id} data-variant="stream">ADD TO CART</a>
</div>
<div className="jamtrack-add-zone">
{fullPriceNotice}
<a className="jamtrack-add-cart button-orange" href="#" data-jamtrack-id={jamtrack.id} data-variant="full">ADD TO CART (FULL)</a>
</div>
<a className="jamtrack-variant-help" href='#'>HELP</a>
</div>`
availabilityNotice = null
if jamtrack.sales_region==context.JK.AVAILABILITY_US
@ -136,7 +160,6 @@ MIX_MODES = context.JK.MIX_MODES
<td className="jamtrack-action">
<div className="jamtrack-action-container">
<div className="jamtrack-actions">
<div className={jamtrackPricesClasses}>$ {jamtrack.price}</div>
{actionBtn}
{availabilityNotice}
</div>
@ -326,12 +349,12 @@ MIX_MODES = context.JK.MIX_MODES
.done((response) =>
@setState({jamtracks: response.jamtracks, next: response.next, searching: false, first_search: false, currentPage: 1, count: response.count})
)
.fail(() =>
.fail((jqXHR) =>
@app.notifyServerError jqXHR, 'Search Unavailable'
@setState({searching: false, first_search: false})
)
)
.fail(() =>
.fail((jqXHR) =>
@app.notifyServerError jqXHR, 'Search Unavailable'
@setState({searching: false, first_search: false})
)
@ -440,6 +463,7 @@ MIX_MODES = context.JK.MIX_MODES
e.preventDefault()
$target = $(e.target)
params = id: $target.attr('data-jamtrack-id')
params.variant = $target.attr('data-variant')
isFree = $(e.target).is('.is_free')
@rest.addJamtrackToShoppingCart(params).done((response) =>
@ -475,6 +499,18 @@ MIX_MODES = context.JK.MIX_MODES
$parent.find('.jamtrack-add-cart').on 'click', @addToCartJamtrack
$parent.find('.license-us-why').on 'click', @licenseUSWhy
$parent.find('.jamtrack-detail-btn').on 'click', @toggleExpanded
$parent.find('.jamtrack-variant-help').on 'click', @showVariantHelp
$parent.find('.jamtrack-upgrade-help').on 'click', @showUpgradeHelp
showVariantHelp: (e) ->
$screen = $('#jamtrackSearch')
e.preventDefault()
context.JK.HelpBubbleHelper.jamtrackVariants($(e.target), $screen)
showUpgradeHelp: (e) ->
$screen = $('#jamtrackSearch')
e.preventDefault()
context.JK.HelpBubbleHelper.jamtrackUpgrade($(e.target), $screen)
toggleExpanded:(e) ->
e.preventDefault()

View File

@ -502,7 +502,7 @@ UserStore = context.UserStore
else if this.state.user.lesson_package_type_id == 'test-drive-2'
explanation =`<span>You are purchasing the TestDrive package of JamClass by JamKazam. This purchase entitles you to take 2 private online music lessons - 1 each from 2 different instructors in the JamClass instructor community. The price of this TestDrive package is $29.99.</span>`
else
alert("You do not have a test drive package selected")
alert("You do not have a test drive package selected: " + this.state.user.lesson_package_type_id )
bookingDetail = `<p>{explanation}

View File

@ -0,0 +1,160 @@
context = window
MIX_MODES = context.JK.MIX_MODES
@PayPalConfirmationScreen = React.createClass({
mixins: [Reflux.listenTo(@AppStore, "onAppInit"), Reflux.listenTo(@UserStore, "onUserChanged")]
render: () ->
content = null
if this.state.sold
if context.jamClient && context.jamClient.IsNativeClient()
platformMessage = `<div>
<p> To play your purchased JamTrack, start a session and then open the JamTrack</p>
<a className="download-jamkazam-wrapper" href="/client#/createSession">
<div className="download-jamkazam">
Click Here to Start a Session
</div>
</a>
</div>`
else
platformMessage =
`<div>
<a href='/client#/jamtrack' className="download-jamkazam-wrapper jt-popup">
<div className="download-jamkazam">
Click Here to Start Using Your JamTrack
</div>
</a>
<a href='/downloads' rel="external" className="download-jamkazam-wrapper" target="_blank">
<div>
Do More With Your JamTrack - Click Here to Download Our Application
</div>
</a>
<a className="back-to-browsing" href="/client#/jamtrack">
or click here to browse more jamtracks
</a>
</div>`
content = `<div className="sold-notice">
<h2>Thank you for your order!</h2>
{platformMessage}
</div>`
else
orderButtons = {"button-orange": true, "place-order-btn": true, disabled: this.state.ordering }
cancelButtons = {"button-grey": true, "cancel": true, disabled: this.state.ordering }
content = `<div>
<h2 className="confirm-header">Confirm PayPal Payment</h2>
<p>You have not yet made a payment via PayPal. Please review your purchase and confirm or cancel.</p>
<div className="controls">
<a href="#" className={classNames(orderButtons)} onClick={this.placeOrder}>CONFIRM PURCHASE WITH
PAYPAL</a>
<a href="#" className={classNames(cancelButtons)} onClick={this.cancel}>CANCEL</a>
<div className="clearall"/>
</div>
<ShoppingCartContents carts={this.state.carts}/>
<div className="controls bottom">
<a href="#" className={classNames(orderButtons)} onClick={this.placeOrder}>CONFIRM PURCHASE WITH
PAYPAL</a>
<a href="#" className={classNames(cancelButtons)} onClick={this.cancel}>CANCEL</a>
<div className="clearall"/>
</div>
</div>`
`<div className="PayPalConfirmationScreen">
<div className="content-body-scroller">
{content}
</div>
</div>`
placeOrder: (e) ->
e.preventDefault()
if this.state.ordering
return
@setState({ordering: true})
console.log("placing order with paypal")
@rest.paypalPlaceOrder()
.done((response) =>
console.log("paypal detail obtained", response)
@setState({sold: true, ordering: false})
context.JK.JamTrackUtils.checkShoppingCart()
@app.refreshUser()
)
.fail((jqXHR) =>
@setState({ordering: false})
if jqXHR.status == 404
context.JK.Banner.showAlert('PayPal Session Over', 'Your PayPal authorization has expired. Please restart the PayPal confirmation process. <a href="/client#/checkoutPayment">Click Here to Checkout Again.</a>')
else if jqXHR.status == 422
response = JSON.parse(jqXHR.responseText)
context.JK.Banner.showAlert('PayPal Purchase Error', 'PayPal: ' + response.message)
else
context.JK.Banner.showAlert('PayPal/Sales Error', 'Please contact support@jamkazam.com')
)
cancelOrder: (e) ->
e.preventDefault()
window.location = '/client#/jamtrack'
getInitialState: () ->
{}
componentDidMount: () ->
componentDidUpdate: () ->
afterShow: (data) ->
rest.getShoppingCarts()
.done((carts) =>
@setState({carts: carts})
if carts.length == 0
window.location = '/client#/jamtrack'
return
@rest.paypalDetail()
.done((response) =>
console.log("paypal detail obtained", response)
)
.fail((jqXHR) =>
if jqXHR.status == 404
context.JK.Banner.showAlert('PayPal Session Over', 'Your PayPal authorization has expired. Please restart the PayPal confirmation process. <a href="/client#/checkoutPayment">Click Here to Checkout Again.</a>')
else if jqXHR.status == 422
response = JSON.parse(jqXHR.responseText)
context.JK.Banner.showAlert('PayPal Purchase Error', 'PayPal: ' + response.message)
else
context.JK.Banner.showAlert('PayPal/Sales Error', 'Please contact support@jamkazam.com')
@app.notifyServerError jqXHR, 'PayPal Communication Error'
)
)
.fail((jqXHR) =>
@app.notifyServerError jqXHR, 'Unable to fetch carts'
)
beforeShow: () ->
this.setState({sold: false})
onAppInit: (@app) ->
@EVENTS = context.JK.EVENTS
@rest = context.JK.Rest()
@logger = context.JK.logger
screenBindings =
'beforeShow': @beforeShow
'afterShow': @afterShow
@app.bindScreen('paypal/confirm', screenBindings)
onUserChanged: (userState) ->
@user = userState?.user
})

View File

@ -69,7 +69,7 @@ JamTrackPlayerStore = reactContext.JamTrackPlayerStore
new window.Fingerprint2().get((result, components) => (
redirectTo = "/api/mixdowns/#{@state.mixdown.id}/download.mp3?file_type=mp3&sample_rate=#{@sampleRate}&download=1&mark=#{result}"
redirectTo = URI.escape(redirectTo)
redirectTo = encodeURIComponent(redirectTo)
AppActions.openExternalUrl(window.location.protocol + '//' + window.location.host + "/signin?redirect-to=#{redirectTo}")
))

View File

@ -6,7 +6,6 @@ rest = context.JK.Rest()
mixins = []
# make sure this is actually us opening the window, not someone else (by checking for MixerStore)
# this check ensures we attempt to listen if this component is created in a popup
reactContext = if window.opener? then window.opener else window
@ -38,7 +37,8 @@ mixins.push(Reflux.listenTo(UserStore, 'onUserChanged'))
weight = switch
when jam_track_track.track_type == 'Master' then 0
when jam_track_track.track_type == 'Click' then 10000
else jam_track_track.position
else
jam_track_track.position
onJamTrackPlayerStoreChanged: (changes) ->
#logger.debug("PopupMediaControls: jamtrack changed", changes)
@ -69,7 +69,6 @@ mixins.push(Reflux.listenTo(UserStore, 'onUserChanged'))
AppActions.openExternalUrl($(e.target).attr('href'))
render: () ->
closeLinkText = null
header = null
extraControls = null
@ -90,17 +89,17 @@ mixins.push(Reflux.listenTo(UserStore, 'onUserChanged'))
if selectedMixdown.client_state?
switch selectedMixdown.client_state
when 'download_fail'
customMixName = `<h5>{selectedMixdown.name}<img src="/assets/content/icon-mix-fail@2X.png" /></h5>`
customMixName = `<h5>{selectedMixdown.name}<img src="/assets/content/icon-mix-fail@2X.png"/></h5>`
when 'downloading'
customMixName = `<h5>Loading selected mix... <img src="/assets/shared/spinner.gif" /></h5>`
customMixName = `<h5>Loading selected mix... <img src="/assets/shared/spinner.gif"/></h5>`
when 'ready'
customMixName = `<h5>{selectedMixdown.name}</h5>`
disabled = false
else
if selectedMixdown.myPackage
customMixName = `<h5>Creating mixdown... <img src="/assets/shared/spinner.gif" /></h5>`
customMixName = `<h5>Creating mixdown... <img src="/assets/shared/spinner.gif"/></h5>`
else
customMixName = `<h5>{selectedMixdown.name}<img src="/assets/content/icon-mix-fail@2X.png" /></h5>`
customMixName = `<h5>{selectedMixdown.name}<img src="/assets/content/icon-mix-fail@2X.png"/></h5>`
else if selectedStem?
if selectedStem.instrument
@ -117,20 +116,20 @@ mixins.push(Reflux.listenTo(UserStore, 'onUserChanged'))
if selectedStem.client_state?
switch selectedStem.client_state
when 'downloading'
customMixName = `<h5>Loading {trackName}... <img src="/assets/shared/spinner.gif" /></h5>`
customMixName = `<h5>Loading {trackName}... <img src="/assets/shared/spinner.gif"/></h5>`
when 'download_fail'
customMixName = `<h5>{trackName}<img src="/assets/content/icon-mix-fail@2X.png" /></h5>`
customMixName = `<h5>{trackName}<img src="/assets/content/icon-mix-fail@2X.png"/></h5>`
when 'ready'
customMixName = `<h5>{trackName}</h5>`
disabled = false
else
customMixName = `<h5>{trackName}<img src="/assets/content/icon-mix-fail@2X.png" /></h5>`
customMixName = `<h5>{trackName}<img src="/assets/content/icon-mix-fail@2X.png"/></h5>`
else
if jamTrack?.client_state == 'downloading'
downloader = `<img src="/assets/shared/spinner.gif" />`
downloader = `<img src="/assets/shared/spinner.gif"/>`
else if jamTrack?.client_state == 'download_fail'
downloader = `<img src="/assets/content/icon-mix-fail@2X.png" />`
downloader = `<img src="/assets/content/icon-mix-fail@2X.png"/>`
jamTrackTypeHeader = `<span>Full JamTrack {downloader}</span>`
@ -153,15 +152,19 @@ mixins.push(Reflux.listenTo(UserStore, 'onUserChanged'))
active = jamTrack.last_mixdown_id == null && jamTrack.last_stem_id == null
myMixdowns.push `
<div key="full-track" className={classNames({'full-track': true, 'mixdown-display': true, 'active' : active})}>
<div key="full-track"
className={classNames({'full-track': true, 'mixdown-display': true, 'active' : active})}>
<div className="mixdown-name">
Full JamTrack
</div>
<div className="mixdown-actions">
<img src="/assets/content/icon_open@2X.png" className="mixdown-play" onClick={boundPlayClick}/>
<a href={this.createJamTrackUrl(jamTrack)} target="_blank" onClick={boundDownloadClick}><img src="/assets/content/icon_download@2X.png" className="mixdown-download"/></a>
<img src ="/assets/content/icon-delete@2X.png" style={{visibility:'hidden'}} className="mixdown-edit" onClick={false} />
<img src ="/assets/content/icon-delete@2X.png" style={{visibility:'hidden'}} className="mixdown-delete" onClick={false} />
<a href={this.createJamTrackUrl(jamTrack)} target="_blank" onClick={boundDownloadClick}><img
src="/assets/content/icon_download@2X.png" className="mixdown-download"/></a>
<img src="/assets/content/icon-delete@2X.png" style={{visibility:'hidden'}} className="mixdown-edit"
onClick={false}/>
<img src="/assets/content/icon-delete@2X.png" style={{visibility:'hidden'}} className="mixdown-delete"
onClick={false}/>
</div>
</div>`
@ -186,26 +189,33 @@ mixins.push(Reflux.listenTo(UserStore, 'onUserChanged'))
if mixdown_package
switch mixdown_package.signing_state
when 'QUIET_TIMEOUT'
action = `<img src="/assets/content/icon-mix-fail@2X.png" className="mixdown-play" onClick={boundErrorClick}/>`
action = `<img src="/assets/content/icon-mix-fail@2X.png" className="mixdown-play"
onClick={boundErrorClick}/>`
when 'QUIET'
action = `<img src="/assets/shared/spinner.gif" className="mixdown-play"/>`
when 'QUEUED'
action = `<img src="/assets/shared/spinner.gif" className="mixdown-play"/>`
when 'QUEUED_TIMEOUT'
action = `<img src="/assets/content/icon-mix-fail@2X.png" className="mixdown-play" onClick={boundErrorClick}/>`
action = `<img src="/assets/content/icon-mix-fail@2X.png" className="mixdown-play"
onClick={boundErrorClick}/>`
when 'SIGNING'
action = `<img src="/assets/shared/spinner.gif" className="mixdown-play"/>`
when 'SIGNING_TIMEOUT'
action = `<img src="/assets/content/icon-mix-fail@2X.png" className="mixdown-play" onClick={boundErrorClick}/>`
action = `<img src="/assets/content/icon-mix-fail@2X.png" className="mixdown-play"
onClick={boundErrorClick}/>`
when 'SIGNED'
action = `<img src="/assets/content/icon_open@2X.png" className="mixdown-play" onClick={boundPlayClick}/>`
action = `<img src="/assets/content/icon_open@2X.png" className="mixdown-play"
onClick={boundPlayClick}/>`
when 'ERROR'
action = `<img src="/assets/content/icon-mix-fail@2X.png" className="mixdown-play" onClick={boundErrorClick}/>`
action = `<img src="/assets/content/icon-mix-fail@2X.png" className="mixdown-play"
onClick={boundErrorClick}/>`
else
action = `<img src="/assets/content/icon-mix-fail@2X.png" className="mixdown-play" onClick={boundErrorClick}/>`
action = `<img src="/assets/content/icon-mix-fail@2X.png" className="mixdown-play"
onClick={boundErrorClick}/>`
if editing
mixdownName = `<input className="edit-name" type="text" defaultValue={mixdown.name} onKeyDown={boundEditKeydown} />`
mixdownName = `<input className="edit-name" type="text" defaultValue={mixdown.name}
onKeyDown={boundEditKeydown}/>`
editIcon = `<img src="/assets/content/icon-save@2X.png" className="mixdown-edit" onClick={boundSaveClick}/>`
else
mixdownName = mixdown.name
@ -213,9 +223,11 @@ mixins.push(Reflux.listenTo(UserStore, 'onUserChanged'))
# create hidden objects to deal with alginment issues using a table
if !editIcon
editIcon = `<img src ="/assets/content/icon-delete@2X.png" style={{visibility:'hidden'}} className="mixdown-edit" onClick={false} />`
editIcon = `<img src="/assets/content/icon-delete@2X.png" style={{visibility:'hidden'}}
className="mixdown-edit" onClick={false}/>`
download = `<a onClick={boundDownloadReadyClick} href={this.downloadMixdownUrl(mixdown)} target="_blank"><img src="/assets/content/icon_download@2X.png" className="mixdown-download" /></a>`
download = `<a onClick={boundDownloadReadyClick} href={this.downloadMixdownUrl(mixdown)} target="_blank"><img
src="/assets/content/icon_download@2X.png" className="mixdown-download"/></a>`
myMixdowns.push `
<div key={mixdown.id} className={classNames({'mixdown-display': true, 'active' : active})}>
@ -227,7 +239,7 @@ mixins.push(Reflux.listenTo(UserStore, 'onUserChanged'))
{download}
{editIcon}
<img src ="/assets/content/icon-delete@2X.png" className="mixdown-delete" onClick={boundDeleteClick} />
<img src="/assets/content/icon-delete@2X.png" className="mixdown-delete" onClick={boundDeleteClick}/>
</div>
</div>`
@ -260,16 +272,21 @@ mixins.push(Reflux.listenTo(UserStore, 'onUserChanged'))
myMixdowns.push `
<div key={track.id} className={classNames({'stem-track' : true, 'mixdown-display': true, 'active' : active})}>
<div className="mixdown-name">
<select className="active-stem-select" name="active-stem-select" onChange={boundStemChange} defaultValue={jamTrack.last_stem_id}>
<select className="active-stem-select" name="active-stem-select" onChange={boundStemChange}
defaultValue={jamTrack.last_stem_id}>
<option key="null" value="">or select a track</option>
{trackOptions}
</select>
</div>
<div className="mixdown-actions">
<img src="/assets/content/icon_open@2X.png" className="mixdown-play" onClick={boundStemActivateClick}/>
<a href={this.createStemUrl(jamTrack.id, jamTrack.last_stem_id)} target="_blank" onClick={boundStemPlayClick}><img src="/assets/content/icon_download@2X.png" className="mixdown-download" /></a>
<img src ="/assets/content/icon-delete@2X.png" style={{visibility:'hidden'}} className="mixdown-edit" onClick={false} />
<img src ="/assets/content/icon-delete@2X.png" style={{visibility:'hidden'}} className="mixdown-delete" onClick={false} />
<a href={this.createStemUrl(jamTrack.id, jamTrack.last_stem_id)} target="_blank"
onClick={boundStemPlayClick}><img src="/assets/content/icon_download@2X.png"
className="mixdown-download"/></a>
<img src="/assets/content/icon-delete@2X.png" style={{visibility:'hidden'}} className="mixdown-edit"
onClick={false}/>
<img src="/assets/content/icon-delete@2X.png" style={{visibility:'hidden'}} className="mixdown-delete"
onClick={false}/>
</div>
</div>`
@ -300,23 +317,29 @@ mixins.push(Reflux.listenTo(UserStore, 'onUserChanged'))
tracks.push(`
<tr className="stem">
<td><img src={context.JK.getInstrumentIcon24(instrumentId)} className="instrument-icon" /> {instrumentDescription} {part}</td>
<td className="mute"><input type="checkbox" className="stem-mute" data-stem-id={track.id} defaultChecked={track.track_type == 'Click'}/></td>
<td><img src={context.JK.getInstrumentIcon24(instrumentId)}
className="instrument-icon"/> {instrumentDescription} {part}</td>
<td className="mute"><input type="checkbox" className="stem-mute" data-stem-id={track.id}
defaultChecked={track.track_type == 'Click'}/></td>
</tr>`)
if jamTrack?.jmep?.Events? && jamTrack.jmep.Events[0].metronome?
# tap-in detected; show user tap-in option
tracks.push(`
<tr className="stem">
<td><img src={context.JK.getInstrumentIcon24('computer')} className="instrument-icon" /> Count-in</td>
<td className="mute"><input type="checkbox" className="stem-mute" data-stem-id="count-in" /></td>
<td><img src={context.JK.getInstrumentIcon24('computer')} className="instrument-icon"/> Count-in</td>
<td className="mute"><input type="checkbox" className="stem-mute" data-stem-id="count-in"/></td>
</tr>`)
stems = `<div key="stems" className="stems">
<table>
<thead>
<col align="left" /><col align="right"/>
<tr><th>TRACKS</th><th className="mute">mute</th></tr>
<col align="left"/>
<col align="right"/>
<tr>
<th>TRACKS</th>
<th className="mute">mute</th>
</tr>
</thead>
<tbody>
{tracks}
@ -328,13 +351,15 @@ mixins.push(Reflux.listenTo(UserStore, 'onUserChanged'))
nameClassData = {field: true}
if @state.createMixdownErrors?
errorHtml = context.JK.reactErrors(@state.createMixdownErrors, {name: 'Mix Name', settings: 'Settings', jam_track: 'JamTrack'})
errorHtml = context.JK.reactErrors(@state.createMixdownErrors,
{name: 'Mix Name', settings: 'Settings', jam_track: 'JamTrack'})
createMixClasses = classNames({'button-orange' : true, 'create-mix-btn' : true, 'disabled' : @state.creatingMixdown})
createMixClasses = classNames({'button-orange': true, 'create-mix-btn': true, 'disabled': @state.creatingMixdown})
mixControls = `
<div key="create-mix" className="create-mix">
<p>Mute or unmute any tracks you like. You can also use the controls below to adjust the tempo or pitch of the JamTrack. Then give your custom mix a name, and click the Create Mix button.</p>
<p>Mute or unmute any tracks you like. You can also use the controls below to adjust the tempo or pitch of the
JamTrack. Then give your custom mix a name, and click the Create Mix button.</p>
{stems}
<div className="field">
<label>TEMPO</label>
@ -410,14 +435,22 @@ mixins.push(Reflux.listenTo(UserStore, 'onUserChanged'))
if @state.showMyMixes
showMyMixesText = `<a className="show-hide-my-mixes" onClick={this.toggleMyMixes}>hide my mixes <div className="details-arrow arrow-up" /></a>`
showMyMixesText = `<a className="show-hide-my-mixes" onClick={this.toggleMyMixes}>hide my mixes
<div className="details-arrow arrow-up"/>
</a>`
else
showMyMixesText = `<a className="show-hide-my-mixes" onClick={this.toggleMyMixes}>show my mixes <div className="details-arrow arrow-down" /></a>`
showMyMixesText = `<a className="show-hide-my-mixes" onClick={this.toggleMyMixes}>show my mixes
<div className="details-arrow arrow-down"/>
</a>`
if @state.showCustomMixes
showMixControlsText = `<a className="show-hide-mix-controls" onClick={this.toggleCustomMixes}>hide mix controls <div className="details-arrow arrow-up" /></a>`
showMixControlsText = `<a className="show-hide-mix-controls" onClick={this.toggleCustomMixes}>hide mix controls
<div className="details-arrow arrow-up"/>
</a>`
else
showMixControlsText = `<a className="show-hide-mix-controls" onClick={this.toggleCustomMixes}>show mix controls <div className="details-arrow arrow-down" /></a>`
showMixControlsText = `<a className="show-hide-mix-controls" onClick={this.toggleCustomMixes}>show mix controls
<div className="details-arrow arrow-down"/>
</a>`
extraControls = `
<div className="extra-controls">
@ -432,20 +465,65 @@ mixins.push(Reflux.listenTo(UserStore, 'onUserChanged'))
</div>`
if helpLink?
helpButton = `<a className="help-link button-grey" href={helpLink} target="_blank">HELP</a>`
`<div className="media-controls-popup">
{header}
<BrowserMediaControls disabled={this.disableLoading}/>
{extraControls}
<div className="actions">
{helpButton}
`<div className="jamtrack-player-main">
<div className="jamtrack-player-controls">
<div className="media-header">
jamtracks web player
</div>
<div className="media-controls-popup">
{header}
<BrowserMediaControls disabled={this.disableLoading}/>
{extraControls}
<div className="actions">
{helpButton}
</div>
</div>
</div>
<div className="helpful-resources">
<div className="helpful-header">
helpful resources
</div>
<div className="helpful-section">
<a href="https://jamkazam.desk.com/customer/portal/articles/2166273-playing-your-jamtracks-in-a-browser" target="_blank">read a web player help article</a><br/>
that explains how to use all the features of this web player
</div>
<div className="helpful-section">
<a href="/client#/jamtrack" target="_blank">go to JamTracks homepage</a><br/>
where you can access all of your JamTracks, search for more JamTracks, and more
</div>
<div className="helpful-section">
<a href="https://itunes.apple.com/us/app/jamtracks/id1060927816?mt=8" target="_blank">download our free iOS app</a><br/>
that you can use to play with your JamTracks on your iPhone, iPad, or iPod Touch
</div>
<div className="helpful-section">
<a href="/downloads" target="_blank">download our free Mac or Windows app</a><br/>
that gives you more powerful features to do more amazing things with your JamTracks
</div>
<div className="helpful-section">
<a href="https://jamkazam.desk.com/customer/en/portal/articles/2126113-playing-your-jamtracks-in-our-free-windows-or-mac-app" target="_blank">review a list of help articles on the Mac/Win app</a><br/>
to understand all the things you can do with our free desktop app
</div>
<div className="helpful-section">
<a onClick={this.seeAllByArtist} target="_blank">see more JamTracks by this artist</a><br/>
to check out other songs you might like
</div>
<div className="helpful-section">
<a href="https://s3.amazonaws.com/jamkazam-public/public/lists/all-jamkazam-jamtracks.pdf" target="_blank">download our complete JamTracks catalog</a><br/>
to browse or search through all the music we have in a convenient PDF file
</div>
</div>
</div>`
# <a className="close-link button-orange" onClick={this.close}>{closeLinkText}</a>
seeAllByArtist: (e) ->
e.preventDefault()
jamTrack = @state.jamTrackState?.jamTrack
console.log("seeAllByArtist context", jamTrack)
window.open('/client?artist=' + encodeURIComponent(jamTrack.original_artist) + '#/jamtrack/search')
windowUnloaded: () ->
JamTrackPlayerActions.windowUnloaded() unless window.DontAutoCloseMedia
@ -464,14 +542,12 @@ mixins.push(Reflux.listenTo(UserStore, 'onUserChanged'))
e.preventDefault()
verificationCheck: () ->
if @state.user?.email_needs_verification
alert("Check your email and click the verification link. Refresh this page when done, and try again.")
return @state.user?.email_needs_verification
downloadMixdownReady: (mixdown, e) ->
if @verificationCheck()
e.preventDefault()
return
@ -494,7 +570,6 @@ mixins.push(Reflux.listenTo(UserStore, 'onUserChanged'))
window.location.protocol + '//' + window.location.host + "/api/mixdowns/#{mixdown.id}/download.mp3?file_type=mp3&sample_rate=48&download=1&mark=#{result}"
activateStem: (e) ->
e.preventDefault()
return if @verificationCheck()
@ -506,7 +581,6 @@ mixins.push(Reflux.listenTo(UserStore, 'onUserChanged'))
if !selectedTrackId? || selectedTrackId == ''
alert("You must pick a track from the dropdown in order to play it.")
else
@setState({editingMixdownId: null})
e.preventDefault()
@ -518,8 +592,38 @@ mixins.push(Reflux.listenTo(UserStore, 'onUserChanged'))
# make this package the active one
JamTrackPlayerActions.openStem(selectedTrackId)
downloadStem: (e) ->
addUpgradeToCart: (jamtrack) ->
console.log("adding upgrade to cart")
params = {id: jamtrack.id, variant: 'download'}
rest.addJamtrackToShoppingCart(params).done((response) =>
console.log("added item to shopping cart. fast_redeem? " + response.fast_redeem)
if response.fast_reedem
if context.JK.currentUserId?
window.open('/client#/redeemComplete')
else
window.open('/client#/redeemSignup')
else
window.open('/client#/shoppingCart')
).fail(((jqxhr) =>
handled = false
if jqxhr.status == 422
body = JSON.parse(jqxhr.responseText)
if body.errors?.cart_id?[0] == 'has already been taken'
console.log("already taken, just show shopping cart")
window.open('/client#/shoppingCart')
return
else if body.errors && body.errors.base
handled = true
alert("You can not have a mix of free and non-free items in your shopping cart.\n\nIf you want to add this new item to your shopping cart, then clear out all current items by clicking on the shopping cart icon and clicking 'delete' next to each item.")
if !handled
alert("Error adding to shoppig cart. " + jqxhr.responseText)
))
downloadStem: (e) ->
if @verificationCheck()
e.preventDefault()
return
@ -528,6 +632,12 @@ mixins.push(Reflux.listenTo(UserStore, 'onUserChanged'))
selectedTrackId = $select.val()
if !@state.jamTrackState.jamTrack.can_download
e.preventDefault()
if confirm("You have not purchased the rights to download a JamTrack. Add them to your shopping cart?")
@addUpgradeToCart(@state.jamTrackState.jamTrack)
return
if !selectedTrackId? || selectedTrackId == ''
e.preventDefault()
alert("You must select a track in order to download and also click the folder icon.")
@ -581,11 +691,17 @@ mixins.push(Reflux.listenTo(UserStore, 'onUserChanged'))
JamTrackPlayerActions.activateNoMixdown(jamtrack)
jamTrackDownload: (jamTrack, e) ->
if @verificationCheck()
e.preventDefault()
return
if !@state.jamTrackState.jamTrack.can_download
e.preventDefault()
if confirm("You have not purchased the rights to download a JamTrack. Add them to your shopping cart?")
@addUpgradeToCart(@state.jamTrackState.jamTrack)
return
if /iPhone|iPad|iPod/i.test(navigator.userAgent)
# fall through
@ -622,14 +738,13 @@ mixins.push(Reflux.listenTo(UserStore, 'onUserChanged'))
mixdownDelete: (mixdown) ->
if @state.editingMixdownId?
@setState({editingMixdownId:null})
@setState({editingMixdownId: null})
return
if confirm("Delete this custom mix?")
JamTrackPlayerActions.deleteMixdown(mixdown)
mixdownError: (mixdown) ->
myPackage = mixdown.myPackage
if myPackage?
@ -709,7 +824,11 @@ mixins.push(Reflux.listenTo(UserStore, 'onUserChanged'))
tracks.push({id: stemId, mute: muted})
)
mixdown = {jamTrackID: @state.jamTrackState.jamTrack.id, name: name, settings: {speed:speed, pitch: pitch, "count-in": count_in, tracks:tracks}}
mixdown = {
jamTrackID: @state.jamTrackState.jamTrack.id,
name: name,
settings: {speed: speed, pitch: pitch, "count-in": count_in, tracks: tracks}
}
JamTrackPlayerActions.createMixdown(mixdown, @createMixdownDone, @createMixdownFail)
@ -743,7 +862,6 @@ mixins.push(Reflux.listenTo(UserStore, 'onUserChanged'))
fetchUserInfo: () ->
rest.getUserDetail()
.done((response) =>
rest.postUserEvent({name: 'jamtrack_web_player_open'})
context.stats.write('web.jamtrack_web_player.open', {
value: 1,
@ -757,13 +875,12 @@ mixins.push(Reflux.listenTo(UserStore, 'onUserChanged'))
rest.userOpenedJamTrackWebPlayer()
$root = $(@getDOMNode())
#context.JK.prodBubble($root.find('.create-mix-btn'), 'first-time-jamtrack-web-player', {}, {positions:['left'], offsetParent: $root})
), 1500)
), 1500)
)
componentDidMount: () ->
$(window).unload(@windowUnloaded)
@root = jQuery(this.getDOMNode())

View File

@ -1,7 +1,7 @@
context = window
logger = context.JK.logger
ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
rest = context.JK.Rest()
mixins = []
@ -523,12 +523,49 @@ mixins.push(Reflux.listenTo(UserStore, 'onUserChanged'))
return if @verificationCheck()
if !@state.jamTrackState.jamTrack.can_download
e.preventDefault()
if confirm("You have not purchased the rights to download a JamTrack. Add them to your shopping cart?")
@addUpgradeToCart(@state.jamTrackState.jamTrack)
return
new window.Fingerprint2().get((result, components) => (
redirectTo = "/api/jamtracks/#{jamTrack.id}/stems/master/download.mp3?file_type=mp3&download=1&mark=#{result}"
redirectTo = URI.escape(redirectTo)
redirectTo = encodeURIComponent(redirectTo)
AppActions.openExternalUrl(window.location.protocol + '//' + window.location.host + "/signin?redirect-to=#{redirectTo}")
))
addUpgradeToCart: (jamtrack) ->
console.log("adding upgrade to cart")
params = {id: jamtrack.id, variant: 'download'}
rest.addJamtrackToShoppingCart(params).done((response) =>
console.log("added item to shopping cart. fast_redeem? " + response.fast_redeem)
if response.fast_reedem
if context.JK.currentUserId?
AppActions.openExternalUrl(window.location.protocol + '//' + window.location.host + '/client#/redeemComplete')
else
AppActions.openExternalUrl(window.location.protocol + '//' + window.location.host + '/client#/redeemSignup')
else
AppActions.openExternalUrl(window.location.protocol + '//' + window.location.host + '/client#/shoppingCart')
).fail(((jqxhr) =>
handled = false
if jqxhr.status == 422
body = JSON.parse(jqxhr.responseText)
if body.errors?.cart_id?[0] == 'has already been taken'
console.log("already taken, just show shopping cart")
AppActions.openExternalUrl(window.location.protocol + '//' + window.location.host + '/client#/shoppingCart')
return
else if body.errors && body.errors.base
handled = true
alert("You can not have a mix of free and non-free items in your shopping cart.\n\nIf you want to add this new item to your shopping cart, then clear out all current items by clicking on the shopping cart icon and clicking 'delete' next to each item.")
if !handled
alert("Error adding to shoppig cart. " + jqxhr.responseText)
))
stemChanged:() ->
stemDownload: (e) ->
e.preventDefault()
@ -537,6 +574,12 @@ mixins.push(Reflux.listenTo(UserStore, 'onUserChanged'))
$select = $(this.getDOMNode()).find('.active-stem-select')
if !@state.jamTrackState.jamTrack.can_download
e.preventDefault()
if confirm("You have not purchased the rights to download a JamTrack. Add them to your shopping cart?")
@addUpgradeToCart(@state.jamTrackState.jamTrack)
return
selectedTrackId = $select.val()
if !selectedTrackId? || selectedTrackId == ''
@ -546,7 +589,7 @@ mixins.push(Reflux.listenTo(UserStore, 'onUserChanged'))
new window.Fingerprint2().get((result, components) => (
redirectTo = "/api/jamtracks/#{@state.jamTrackState.jamTrack.id}/stems/#{selectedTrackId}/download.mp3?file_type=mp3&download=1&mark=#{result}"
redirectTo = URI.escape(redirectTo)
redirectTo = encodeURIComponent(redirectTo)
AppActions.openExternalUrl(window.location.protocol + '//' + window.location.host + "/signin?redirect-to=#{redirectTo}")
))
@ -591,7 +634,7 @@ mixins.push(Reflux.listenTo(UserStore, 'onUserChanged'))
if browserPackage?.signing_state == 'SIGNED'
new window.Fingerprint2().get((result, components) => (
redirectTo = "/api/mixdowns/#{mixdown.id}/download.mp3?file_type=mp3&sample_rate=48&download=1&mark=#{result}"
redirectTo = URI.escape(redirectTo)
redirectTo = encodeURIComponent(redirectTo)
AppActions.openExternalUrl(window.location.protocol + '//' + window.location.host + "/signin?redirect-to=#{redirectTo}")
))
else
@ -716,7 +759,6 @@ mixins.push(Reflux.listenTo(UserStore, 'onUserChanged'))
setTimeout(@resizeWindow, 1000)
shouldComponentUpdate: () ->
console.log("THIS UNLOADED", @unloaded)
return !@unloaded
resizeWindow: () =>

View File

@ -36,6 +36,10 @@ if accessOpener
# this.setState(chatMixer: mixers.chatMixer)
onRecordingStateChanged: (recordingState) ->
if @unloaded
#console.log("PopupMediaControls unloaded. ignore onMixersChnaged")
return
this.setState(isRecording: recordingState.isRecording, recordedOnce: this.state.recordedOnce || recordingState.isRecording)
startStopRecording: () ->
@ -165,6 +169,9 @@ if accessOpener
</div>`
windowUnloaded: () ->
@unloaded = true
window.unloaded = true
window.opener.RecordingActions.recordingControlsClosed()
onChatHelp: (e) ->
@ -215,6 +222,9 @@ if accessOpener
$root = jQuery(this.getDOMNode())
$includeChat = $root.find('#include-chat')
shouldComponentUpdate: () ->
return !@unloaded
resizeWindow: () =>
$container = $('#minimal-container')
width = $container.width()

View File

@ -119,7 +119,7 @@ ConfigureTracksActions = @ConfigureTracksActions
$connectionState,
'SessionStatsHover',
() =>
{participant: {client_id: this.props.clientId, user: name: 'You', possessive: 'Your'}, }
{myTrack: true, participant: {client_id: this.props.connStatsClientId, user: name: 'You', possessive: 'Your'}, }
,
{width:385, positions:['right', 'left'], offsetParent:$root.closest('.screen'), extraClasses: 'self'})

View File

@ -9,15 +9,29 @@ MixerActions = context.MixerActions
session = sessionMixers.session
mixers = sessionMixers.mixers
noAudioUsers = mixers.noAudioUsers
clientsWithAudioOverride = mixers.clientsWithAudioOverride
participants = []
if session.inSession()
self = session.getParticipant(@app.clientId)
myJamBlasterClientId = null
if self? && self.client_role == 'child' && self.parent_client_id?
myJamBlasterClientId = self.parent_client_id
for participant in session.otherParticipants()
#if participant.is_jamblaster
#continue
if myJamBlasterClientId? && participant.client_id == myJamBlasterClientId
# don't show my parent jamblaster in 'others'
continue
if participant.client_role == 'child' && participant.parent_client_id?
#participant.parent = session.getParticipant(participant.parent_client_id)
# don't show children nodes
continue
if participant.client_id == @app.clientId
participant.user.possessive = "Your"
@ -55,6 +69,10 @@ MixerActions = context.MixerActions
name = "#{name}: #{instrumentDescription}"
noAudio = false
if !clientsWithAudioOverride[participant.client_id]
noAudio = noAudioUsers[participant.client_id]
participantState = {
participant: participant,
tracks: tracks,
@ -63,7 +81,7 @@ MixerActions = context.MixerActions
instrumentIcon: instrumentIcon,
photoUrl: photoUrl,
hasMixer: hasMixer,
noAudio: noAudioUsers[participant.client_id]
noAudio: noAudio
}
MixerActions.missingPeerMixer(participant.client_id) unless hasMixer

View File

@ -81,7 +81,7 @@ MixerActions = @MixerActions
<div>Volume</div>
<div>{monitorVolumeLeft}dB</div>
</div>
<SessionTrackGain mixers={this.state.inputGroupMixers} gainType='music' />
<SessionTrackGain mixers={this.state.inputGroupMixers} gainType='music' controlGroup="music" />
<div className={monitorMuteClasses} data-control="mute" onClick={this.handleAudioInputMute}/>
<input type="checkbox" name="mute"/>
@ -108,7 +108,7 @@ MixerActions = @MixerActions
<div>Volume</div>
<div>{chatVolumeLeft}dB</div>
</div>
<SessionTrackGain mixers={this.state.chatGroupMixers} />
<SessionTrackGain mixers={this.state.chatGroupMixers} controlGroup="chat" />
<div className={chatMuteClasses} data-control="mute" onClick={this.handleChatMute}/>
<input type="checkbox" name="mute"/>

View File

@ -143,7 +143,7 @@ StatsInfo = {
audio = @state.stats?.audio
aggregateTag = null
if aggregate?
if aggregate? && !this.props.myTrack
if aggregate.latency
aggregateStats.push(@stat(aggregate, 'aggregate', 'Tot Latency', 'latency', Math.round(aggregate.latency)))
@ -173,10 +173,16 @@ StatsInfo = {
audio_type = 'WDM'
else if audio_long.indexOf('core') > -1
audio_type = 'CoreAudio'
else if audio_long.indexOf('alsa') > -1
audio_type = 'JamBlaster'
audioStats.push(@stat(audio, 'audio', 'Gear Driver', 'audio_in_type', audio_type))
if audio.framesize?
framesize = '?'
if audio.framesize == 2.5
if audio.framesize == 1.0
framesize = '1 ms'
else if audio.framesize == 2.0
framesize = '1 ms'
else if audio.framesize == 2.5
framesize = '2.5 ms'
else if audio.framesize == 5
framesize = '5 ms'
@ -185,7 +191,7 @@ StatsInfo = {
audioStats.push(@stat(audio, 'audio', 'Frame Size', 'framesize', framesize))
networkTag = null
if network?
if network? && !this.props.myTrack
if network.ping?
networkStats.push(@stat(network, 'network', 'Latency', 'ping', (network.ping / 2).toFixed(1) + ' ms'))
if network.audiojq_median?
@ -238,7 +244,11 @@ StatsInfo = {
onStatsChanged: (stats) ->
stats = window.SessionStatsStore.stats
if stats?
clientStats = stats[@props.participant.client_id]
if stats.parent?
# if we have a parent, then use stats from the JamBlaster (parent), not ourselves. Otherwise we'll get bad stats (no Audio etc)
clientStats = stats.parent[@props.participant.client_id]
else
clientStats = stats[@props.participant.client_id]
else
clientStats = null
@setState({stats: clientStats})

View File

@ -8,6 +8,7 @@ MIX_MODES = context.JK.MIX_MODES
propTypes: {
gainType: React.PropTypes.string
controlGroup: React.PropTypes.string
}
getInitialState: () ->
@ -22,12 +23,11 @@ MIX_MODES = context.JK.MIX_MODES
mixers = @state.mixers.mixer
# if this is a media track, jam track , or media category, affect volume of both mixer and opposing mixer
if @state.mixers.mixer.group_id == ChannelGroupIds.MediaTrackGroup || @state.mixers.mixer.group_id == ChannelGroupIds.JamTrackGroup || ((@state.mixers.mixer.group_id == ChannelGroupIds.MonitorCatGroup || @state.mixers.mixer.group_id == ChannelGroupIds.MasterCatGroup) && @state.mixers.mixer.name == CategoryGroupIds.MediaTrack)
MixerActions.faderChanged(data, [@state.mixers.mixer, @state.mixers.oppositeMixer], @props.gainType)
MixerActions.faderChanged(data, [@state.mixers.mixer, @state.mixers.oppositeMixer], @props.gainType, @props.controlGroup)
else
MixerActions.faderChanged(data, mixers, @props.gainType)
MixerActions.faderChanged(data, mixers, @props.gainType, @props.controlGroup)
render: () ->
# mixer can be a single item or array

View File

@ -0,0 +1,106 @@
context = window
MIX_MODES = context.JK.MIX_MODES
@ShoppingCartContents = React.createClass({
mixins: [Reflux.listenTo(@AppStore, "onAppInit"), Reflux.listenTo(@UserStore, "onUserChanged")]
render: () ->
carts = []
if this.props.carts?
if this.props.carts.length == 0
carts = `<div className="no-cart-items">You have nothing in your cart</div>`
else
taxRate = 0
if this.props.tax
taxRate = 0.0825
estimatedTax = 0
estimatedTotal = 0
for cart in this.props.carts
cart_quantity = cart.product_info.quantity - cart.product_info.marked_for_redeem
estimatedTax += cart.product_info.price * cart_quantity * taxRate
estimatedTotal += cart.product_info.price * cart_quantity
estimatedTax = Math.round(estimatedTax * 100) / 100
estimatedTotal = Math.round((estimatedTotal + estimatedTax) * 100) / 100
for cart in this.props.carts
console.log("CART", cart)
freeNotice = null
if cart.product_info.free
freeNotice = `<span className="first-one-free">| (first one free)</span>`
carts.push(`<div className="cart-item" key={cart.id}>
<div className="cart-item-caption">
<span>{cart.product_info.sale_display}</span>
{freeNotice}
</div>
<div className="cart-item-price">
$ {Number(cart.product_info.real_price).toFixed(2)}
</div>
<div className="cart-item-quantity">
{cart.quantity}
</div>
<div className="clearall"/>
</div>`)
carts.push(`<div className="cart-item tax-total" key={'tax'}>
<div className="cart-item-caption">
<span>Tax</span>
</div>
<div className="cart-item-price">
$ {estimatedTax.toFixed(2)}
</div>
<div className="cart-item-quantity">
</div>
<div className="clearall"/>
</div>`)
carts.push(`<div className="cart-item total" key={'total'}>
<div className="cart-item-caption">
<span>Total</span>
</div>
<div className="cart-item-price">
$ {estimatedTotal.toFixed(2)}
</div>
<div className="cart-item-quantity">
</div>
<div className="clearall"/>
</div>`)
else
carts = `<div className="loading-indicator">Loading...</div>`
`<div className="shopping-cart-contents">
<div className="order-items-page">
<div className="cart-items">
<div className="cart-item-caption">
<span>YOUR ORDER INCLUDES:</span>
</div>
<div className="cart-item-price">
<span>PRICE</span>
</div>
<div className="cart-item-quantity">
<span>QUANTITY</span>
</div>
<div className="clearall"></div>
{carts}
<div className="clearall"></div>
</div>
</div>
</div>`
onAppInit: (@app) ->
@EVENTS = context.JK.EVENTS
@rest = context.JK.Rest()
@logger = context.JK.logger
onUserChanged: (userState) ->
@user = userState?.user
})

View File

@ -43,6 +43,7 @@ proficiencyDescriptionMap = {
TILE_RATINGS: 'ratings'
TILE_PRICES: 'prices'
visible: false
profileClipboard: null
TILES: ['about', 'experience', 'samples', 'ratings', 'prices']
@ -65,11 +66,26 @@ proficiencyDescriptionMap = {
@root = $(@getDOMNode())
@screen = $('#teacher-profile')
@starbox()
@clipboard()
componentDidUpdate:() ->
@starbox()
context.JK.popExternalLinks(@root)
@clipboard()
clipboard: () ->
$profileLink = @root.find('.copy-profile-link')
if $profileLink.length > 0 && !@profileClipboard?
# mount it
@profileClipboard = new Clipboard($profileLink.get(0), {
text: =>
return context.JK.makeAbsolute('/client#/teacher/profile/' + @state.user.teacher?.id)
})
else if $profileLink.length == 0 && @profileClipboard?
@profileClipboard.destroy()
@profileClipboard = null
starbox:() ->
$ratings = @root.find('.ratings-box')
@ -230,7 +246,9 @@ proficiencyDescriptionMap = {
biography = biography.replace(/\n/g, "<br/>")
`<div className="section bio">
<h3>Teacher Profile {this.editProfileLink('edit profile', 'introduction')}</h3>
<a className="copy-profile-link button-orange" href='#' onClick={this.copyProfileLink}>
COPY PROFILE URL TO CLIPBOARD
</a><h3>Teacher Profile {this.editProfileLink('edit profile', 'introduction')}</h3>
<div className="section-content">
<div dangerouslySetInnerHTML={{__html: biography}}></div>
</div>
@ -694,6 +712,16 @@ proficiencyDescriptionMap = {
</div>
</div>`
copyProfileLink: (e) ->
e.preventDefault()
@app.layout.notify({
title: 'Teacher Profile Link Copied',
text: "Your clipboard now has a link to this teacher that you can share with anyone."
})
selectionMade: (selection, e) ->
e.preventDefault()

View File

@ -14,4 +14,5 @@ context = window
metronomeChanged: {}
deadUserRemove: {}
missingPeerMixer: {}
clientsWithAudio: {}
})

View File

@ -11,4 +11,5 @@ context = window
abortedRecording: {}
openRecordingControls: {}
recordingControlsClosed: {}
mixTransferred: {}
})

View File

@ -8,7 +8,7 @@ MIX_MODES = context.JK.MIX_MODES;
@MixerHelper = class MixerHelper
constructor: (@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @mixMode) ->
constructor: (@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @clientsWithAudioOverride, @mixMode) ->
@mixMode = MIX_MODES.PERSONAL # TODO - remove mixMode from MixerHelper? Or at least stop using it in most functions
@app = @session.app
@mixersByResourceId = {}
@ -622,13 +622,12 @@ MIX_MODES = context.JK.MIX_MODES;
return mixerPair.personal
findMixerForTrack: (client_id, track, myTrack, mode = MIX_MODES.PERSONAL) ->
findMixerForTrack: (client_id, track, myTrack, mode) ->
mixer = null # what is the best mixer for this track/client ID?
oppositeMixer = null # what is the corresponding mixer in the opposite mode?
vuMixer = null
muteMixer = null
if myTrack
# when it's your track, look it up by the backend resource ID
mixer = @getMixerByTrackId(track.client_track_id, mode)
@ -674,7 +673,7 @@ MIX_MODES = context.JK.MIX_MODES;
oppositeMixer = oppositeMixers[ChannelGroupIds.UserMusicInputGroup][0]
if !oppositeMixer
logger.warn("unable to find UserMusicInputGroup corresponding to PeerAudioInputMusicGroup mixer", mixer, @personalMixers )
logger.warn("unable to find UserMusicInputGroup corresponding to PeerAudioInputMusicGroup mixer", mixer, @personalMixers)
when MIX_MODES.PERSONAL
mixers = @groupedMixersForClientId(client_id, [ ChannelGroupIds.UserMusicInputGroup], {}, MIX_MODES.PERSONAL)
@ -693,6 +692,8 @@ MIX_MODES = context.JK.MIX_MODES;
logger.error("personaol: found remote mixer that was not of groupID: PeerAudioInputMusicGroup", client_id, track.client_track_id, mixer)
#vuMixer = oppositeMixer; # for personal mode, use the PeerAudioInputMusicGroup's VUs
else
logger.error("no UserMusicInputGroup for client_id #{client_id} in PERSONAL mode", mixers)
{
mixer: mixer,
@ -729,26 +730,43 @@ MIX_MODES = context.JK.MIX_MODES;
originalVolume
faderChanged: (data, mixers, gainType) ->
faderChanged: (data, mixers, gainType, controlGroup) ->
mixers = [mixers] unless $.isArray(mixers)
originalVolume = @getOriginalVolume(mixers, gainType)
for mixer in mixers
broadcast = !(data.dragging) # If fader is still dragging, don't broadcast
mixer = @fillTrackVolumeObject(mixer.id, mixer.mode, broadcast)
if controlGroup?
mixers = [mixers[0]]
relative = gainType == 'music' && (mixer.name == CategoryGroupIds.UserMedia || mixer.name == CategoryGroupIds.MediaTrack)
for mixer in mixers
broadcast = !(data.dragging) # If fader is still dragging, don't broadcast
mixer = @fillTrackVolumeObject(mixer.id, mixer.mode, broadcast)
@setMixerVolume(mixer, data.percentage, relative, originalVolume)
relative = gainType == 'music' && (mixer.name == CategoryGroupIds.UserMedia || mixer.name == CategoryGroupIds.MediaTrack)
# keep state of mixer in sync with backend
mixer = @getMixer(mixer.id, mixer.mode)
mixer.volume_left = context.trackVolumeObject.volL
@setMixerVolume(mixer, data.percentage, relative, originalVolume, controlGroup)
#if groupId == ChannelGroupIds.UserMusicInputGroup
# # there may be other mixers with this same ID in the case of a Peer Music Stream, so update them as well
# context.JK.FaderHelpers.setFaderValue(mixerId, data.percentage)
# keep state of mixer in sync with backend
mixer = @getMixer(mixer.id, mixer.mode)
mixer.volume_left = context.trackVolumeObject.volL
else
for mixer in mixers
broadcast = !(data.dragging) # If fader is still dragging, don't broadcast
mixer = @fillTrackVolumeObject(mixer.id, mixer.mode, broadcast)
relative = gainType == 'music' && (mixer.name == CategoryGroupIds.UserMedia || mixer.name == CategoryGroupIds.MediaTrack)
@setMixerVolume(mixer, data.percentage, relative, originalVolume)
# keep state of mixer in sync with backend
mixer = @getMixer(mixer.id, mixer.mode)
mixer.volume_left = context.trackVolumeObject.volL
#if groupId == ChannelGroupIds.UserMusicInputGroup
# # there may be other mixers with this same ID in the case of a Peer Music Stream, so update them as well
# context.JK.FaderHelpers.setFaderValue(mixerId, data.percentage)
initGain: (mixer) ->
if $.isArray(mixer)
@ -791,7 +809,7 @@ MIX_MODES = context.JK.MIX_MODES;
mixer = @getMixer(mixer.id, mixer.mode)
mixer.loop = context.trackVolumeObject.loop
setMixerVolume: (mixer, volumePercent, relative, originalVolume) ->
setMixerVolume: (mixer, volumePercent, relative, originalVolume, controlGroup) ->
###
// The context.trackVolumeObject has been filled with the mixer values
// that go with mixerId, and the range of that mixer
@ -821,7 +839,15 @@ MIX_MODES = context.JK.MIX_MODES;
else
context.trackVolumeObject.volL = newVolume
context.trackVolumeObject.volR = newVolume
context.jamClient.SessionSetControlState(mixer.id, mixer.mode);
if controlGroup?
if mixer.mode == MIX_MODES.PERSONAL
controlGroupsArg = 0
else
controlGroupsArg = 1
context.jamClient.setSessionMixerCategoryPlayoutState(controlGroup == 'music', controlGroupsArg);
else
context.jamClient.SessionSetControlState(mixer.id, mixer.mode);
percentFromMixerValue: (min, max, value) ->
try

View File

@ -98,7 +98,7 @@ rest = context.JK.Rest()
<li>And more</li>
</ul>
<p>
JamTracks sell for $1.99 each. Musicians love to play with these, and typically buy a few at a
JamTracks sell for $1.99 each ($4.99 to include ability to download). Musicians love to play with these, and typically buy a few at a
time. Imagine that you are selling a set of guitar strings to an electric guitar player. As a

View File

@ -72,7 +72,7 @@ rest = context.JK.Rest()
<a href="/client#/jamtrack/search" onClick={this.redeem} className="cta-free-jamtrack" alt="ClICK HERE TO PICK YOUR FIRST JAMTRACK FREE!">
{img}
</a>
<span className="value-indicator">$1.99 value</span>
<span className="value-indicator">#{this.props.jam_track.download_price} value</span>
</div>
<br/>
<div className="browse-band">

View File

@ -35,10 +35,10 @@ rest = context.JK.Rest()
if loggedIn
loggedInCtaButton = `<button className={classNames({'cta-button' : true, 'processing': this.state.processing})} onClick={this.ctaClick}>{ctaButtonText}</button>`
if !@isFree()
loggedInPriceAdvisory = `<div className="price-advisory">${this.props.jam_track.price}</div>`
loggedInPriceAdvisory = `<div className="price-advisory">${this.props.jam_track.download_price}</div>`
else
if !@isFree()
loggedOutPriceAdvisory = `<div className="price-advisory">${this.props.jam_track.price}</div>`
loggedOutPriceAdvisory = `<div className="price-advisory">${this.props.jam_track.download_price}</div>`
if this.state.loginErrors?
for key, value of this.state.loginErrors

View File

@ -108,7 +108,7 @@ context = window
<iframe src="//www.youtube.com/embed/ysptXwFYDhQ" frameborder="0" allowfullscreen="allowfullscreen"/>
</div>
</div>
<p>Your first JamTrack is free, and after that JamTracks are just $1.99 each.</p>
<p>Your first JamTrack is free, and after that JamTracks are just $1.99/$4.99 each.</p>
<div className="clearall"/>
</div>
</div>

View File

@ -22,7 +22,7 @@ badCode = 'This is not a valid code. Please carefully re-enter the code and try
if errorText? && errorText.indexOf('already claimed') > -1
errorText = 'This card has already been claimed. If you believe this is in error, please email us at support@jamkazam.com to report this problem.'
buttonClassnames = classNames({'redeem-giftcard': true, 'button-orange': true, disabled: @state.processing || @state.done })

View File

@ -0,0 +1,16 @@
context = window
rest = context.JK.Rest()
@SimpleJamClassPage = React.createClass({
render: () ->
`<div className="container">
<div className="video-wrapper">
<div className="video-container">
<iframe src="//www.youtube.com/embed/6lICn4g5X-Q" frameborder="0" allowfullscreen="allowfullscreen"/>
</div>
</div>
</div>`
})

View File

@ -0,0 +1,16 @@
context = window
rest = context.JK.Rest()
@SimpleJamTracksPage = React.createClass({
render: () ->
`<div className="container">
<div className="video-wrapper">
<div className="video-container">
<iframe src="//www.youtube.com/embed/-rHfJggbgqk" frameborder="0" allowfullscreen="allowfullscreen"/>
</div>
</div>
</div>`
})

View File

@ -28,6 +28,12 @@ MIDI_TRACK = context.JK.MIDI_TRACK
if session.inSession()
participant = session.getParticipant(@app.clientId)
connStatsClientId = @app.clientId
if participant.client_role == 'child' && participant.parent_client_id?
participant.parent = session.getParticipant(participant.parent_client_id)
connStatsClientId = participant.parent_client_id
if participant
photoUrl = context.JK.resolveAvatarUrl(participant.user.photo_url);
@ -79,7 +85,7 @@ MIDI_TRACK = context.JK.MIDI_TRACK
associatedVst = vst
break
tracks.push({track: track, mixerFinder: mixerFinder, mixers: mixerData, hasMixer:hasMixer, name: name, trackName: trackName, instrumentIcon: instrumentIcon, photoUrl: photoUrl, clientId: participant.client_id, associatedVst: associatedVst})
tracks.push({track: track, mixerFinder: mixerFinder, mixers: mixerData, hasMixer:hasMixer, name: name, trackName: trackName, instrumentIcon: instrumentIcon, photoUrl: photoUrl, clientId: participant.client_id, associatedVst: associatedVst, connStatsClientId: connStatsClientId})
else
logger.warn("SessionMyTracks: unable to find participant")

View File

@ -1,6 +1,7 @@
$ = jQuery
context = window
logger = context.JK.logger
RecordingActions = @RecordingActions
SessionActions = @SessionActions
JamBlasterActions = @JamBlasterActions
@ -27,6 +28,9 @@ JamBlasterActions = @JamBlasterActions
JamBlasterActions.pairState(map)
else if map.cmd == 'jamblaster_tracks_updated'
JamBlasterActions.jamblasterTracksUpdated()
else if map.cmd == 'file_xfer_from_parent'
if map.filename && map.filename.indexOf('RT-mix.wav') > -1
RecordingActions.mixTransferred()
}
)

View File

@ -81,7 +81,7 @@ SessionStore = context.SessionStore
query.start = next
return query
c: (lessonSessionId) ->
initializeLesson: (lessonSessionId) ->
@lessonSessionId = lessonSessionId
@channelType = 'lesson'

View File

@ -20,6 +20,7 @@ rest = context.JK.Rest()
checkingMissingPeers : {}
missingMixerPeers : {}
recheckTimeout : null
clientsWithAudioOverride : {}
init: ->
# Register with the app store to get @app
@ -38,6 +39,7 @@ rest = context.JK.Rest()
this.listenTo(context.MixerActions.metronomeChanged, this.onMetronomeChanged)
this.listenTo(context.MixerActions.deadUserRemove, this.onDeadUserRemove)
this.listenTo(context.MixerActions.missingPeerMixer, this.onMissingPeerMixer)
this.listenTo(context.MixerActions.clientsWithAudio, this.onClientsWithAudio)
context.JK.HandleVolumeChangeCallback2 = @handleVolumeChangeCallback
context.JK.HandleMetronomeCallback2 = @handleMetronomeCallback
@ -76,7 +78,7 @@ rest = context.JK.Rest()
# metroSound = args.sound
SessionActions.syncWithServer()
@mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @mixers?.mixMode || MIX_MODES.PERSONAL)
@mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @clientsWithAudioOverride, @mixers?.mixMode || MIX_MODES.PERSONAL)
@issueChange()
@ -132,7 +134,7 @@ rest = context.JK.Rest()
@masterMixers = context.jamClient.SessionGetAllControlState(true);
@personalMixers = context.jamClient.SessionGetAllControlState(false);
@mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @mixers?.mixMode || MIX_MODES.PERSONAL)
@mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @clientsWithAudioOverride, @mixers?.mixMode || MIX_MODES.PERSONAL)
@issueChange()
@ -146,9 +148,9 @@ rest = context.JK.Rest()
# simulate a state change to cause a UI redraw
@issueChange()
onFaderChanged: (data, mixers, gainType) ->
onFaderChanged: (data, mixers, gainType, controlGroup) ->
@mixers.faderChanged(data, mixers, gainType)
@mixers.faderChanged(data, mixers, gainType, controlGroup)
@issueChange()
@ -171,7 +173,7 @@ rest = context.JK.Rest()
@metro.sound = sound
context.jamClient.SessionSetMetronome(@metro.tempo, @metro.sound, 1, 0);
@mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @mixers?.mixMode || MIX_MODES.PERSONAL)
@mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @clientsWithAudioOverride, @mixers?.mixMode || MIX_MODES.PERSONAL)
@issueChange()
onDeadUserRemove: (clientId) ->
@ -193,6 +195,9 @@ rest = context.JK.Rest()
@issueChange()
onClientsWithAudio: (clients) ->
@clientsWithAudioOverride = clients
onMissingPeerMixer: (clientId) ->
missingPeerAttempts = @missingMixerPeers[clientId]
@ -219,7 +224,7 @@ rest = context.JK.Rest()
@masterMixers = context.jamClient.SessionGetAllControlState(true);
@personalMixers = context.jamClient.SessionGetAllControlState(false);
logger.debug("MixerStore: recheckForMixers")
@mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @mixers?.mixMode || MIX_MODES.PERSONAL)
@mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @clientsWithAudioOverride, @mixers?.mixMode || MIX_MODES.PERSONAL)
@issueChange()
onInitGain: (mixer) ->
@ -234,7 +239,7 @@ rest = context.JK.Rest()
logger.debug("MixerStore: onMixersChanged")
@mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @mixers?.mixMode || MIX_MODES.PERSONAL)
@mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @clientsWithAudioOverride, @mixers?.mixMode || MIX_MODES.PERSONAL)
SessionActions.mixersChanged.trigger(type, text, @mixers.getTrackInfo())

View File

@ -33,10 +33,11 @@ BackendToFrontendFPS = {
this.trigger({isRecording: @recordingModel.isRecording()})
onStartRecording: (recordVideo, recordChat) ->
frameRate = context.jamClient.GetCurrentVideoFrameRate() || 0;
frameRate = BackendToFrontendFPS[frameRate]
frameRate = 0
if recordVideo
if context.jamClient.GetCurrentVideoFrameRate?
frameRate = context.jamClient.GetCurrentVideoFrameRate() || 0;
frameRate = BackendToFrontendFPS[frameRate]
NoVideoRecordActive = 0
WebCamRecordActive = 1
@ -49,12 +50,14 @@ BackendToFrontendFPS = {
onStartingRecording: (details) ->
details.cause = 'starting'
@mixTransferred = false
this.trigger(details)
@popupRecordingControls() unless @recordingWindow?
onStartedRecording: (details) ->
details.cause = 'started'
@mixTransferred = false
this.trigger(details)
@popupRecordingControls() unless @recordingWindow?
@ -92,6 +95,9 @@ BackendToFrontendFPS = {
logger.debug("recording controls closed")
@recordingWindow = null
onMixTransferred: () ->
@mixTransferred = true
popupRecordingControls: () ->
logger.debug("poupRecordingControls")
@recordingWindow = window.open("/popups/recording-controls", 'Recording', 'scrollbars=yes,toolbar=no,status=no,height=315,width=340')

View File

@ -6,6 +6,7 @@ EVENTS = context.JK.EVENTS
MIX_MODES = context.JK.MIX_MODES
SessionActions = @SessionActions
MixerActions = @MixerActions
SessionStatThresholds = gon.session_stat_thresholds
NetworkThresholds = SessionStatThresholds.network
@ -16,7 +17,9 @@ AggregateThresholds = SessionStatThresholds.aggregate
@SessionStatsStore = Reflux.createStore(
{
listenables: @SessionStatsActions
rawStats: null
rawStats: null,
parentStats: null
clientsWithAudio: null
init: ->
# Register with the app store to get @app
@ -24,8 +27,9 @@ AggregateThresholds = SessionStatThresholds.aggregate
onAppInit: (@app) ->
onPushStats: (stats) ->
onPushStats: (stats, parentStats) ->
@rawStats = stats
@parentStats = parentStats
@changed()
classify: (holder, field, threshold) ->
@ -65,17 +69,15 @@ AggregateThresholds = SessionStatThresholds.aggregate
else
holder[fieldLevel] = 'good'
changed: () ->
@stats = {}
classifyStats: (statBag) ->
container = {}
self = null
for participant in @rawStats
for participant in statBag
if participant.id == @app.clientId
self = participant
break
for participant in @rawStats
for participant in statBag
aggregate = {}
@ -93,6 +95,9 @@ AggregateThresholds = SessionStatThresholds.aggregate
network = participant.network
if network?
if network.audio_bitrate_rx > 0 && network.audio_bitrate_tx > 0
@clientsWithAudio[participant.id] = true
@classify(network, 'audiojq_median', NetworkThresholds)
@classify(network, 'jitter_var', NetworkThresholds)
@classify(network, 'audio_bitrate_rx', NetworkThresholds)
@ -151,8 +156,23 @@ AggregateThresholds = SessionStatThresholds.aggregate
else
participant.classification = 'unknown'
@stats[participant.id] = participant
container[participant.id] = participant
return container
changed: () ->
@clientsWithAudio = {}
@stats = {}
@stats = @classifyStats(@rawStats)
if @parentStats
@stats.parent = @classifyStats(@parentStats)
else
@stats.parent = null
MixerActions.clientsWithAudio(@clientsWithAudio)
# see if we can reset noAudio
@trigger(@stats)
}
)

View File

@ -728,8 +728,19 @@ ConfigureTracksActions = @ConfigureTracksActions
context.jamClient.SessionRegisterCallback("JK.HandleBridgeCallback2");
context.jamClient.RegisterRecordingCallbacks("JK.HandleRecordingStartResult", "JK.HandleRecordingStopResult", "JK.HandleRecordingStarted", "JK.HandleRecordingStopped", "JK.HandleRecordingAborted");
context.jamClient.SessionSetConnectionStatusRefreshRate(1000);
clientRole = context.jamClient.getClientParentChildRole();
parentClientId = context.jamClient.getParentClientId();
logger.debug("role when joining session: #{clientRole}, parent client id #{parentClientId}")
#context.JK.HelpBubbleHelper.jamtrackGuideSession($screen.find('li.open-a-jamtrack'), $screen)
if clientRole == 0
clientRole = 'child'
else if clientRole == 1
clientRole = 'parent'
if clientRole == '' || !clientRole
clientRole = null
# subscribe to events from the recording model
@recordingRegistration()
@ -741,6 +752,8 @@ ConfigureTracksActions = @ConfigureTracksActions
as_musician: true,
tracks: @userTracks,
session_id: @currentSessionId,
client_role: clientRole,
parent_client_id: parentClientId
audio_latency: context.jamClient.FTUEGetExpectedLatency().latency
})
.done((response) =>
@ -840,8 +853,11 @@ ConfigureTracksActions = @ConfigureTracksActions
@backendStatsInterval = window.setInterval((() => (@updateBackendStats())), 1000)
updateBackendStats: () ->
connectionStats = window.jamClient.getConnectionDetail('')
SessionStatsActions.pushStats(connectionStats)
connectionStats = window.jamClient.getConnectionDetail('', false)
parentConnectionStats = window.jamClient.getConnectionDetail('', true)
#console.log("CONNECTION STATES", connectionStats)
#console.log("PARENT STATES", parentConnectionStats)
SessionStatsActions.pushStats(connectionStats, parentConnectionStats)
trackChanges: (header, payload) ->
if @currentTrackChanges < payload.track_changes_counter

View File

@ -102,7 +102,7 @@
var details = { clientId: app.clientId, reason: 'rest', detail: arguments, isRecording: false }
$self.triggerHandler('startedRecording', details);
currentlyRecording = false;
context.RecordingActions.startedRecording(details);
context.RecordingActions.startedRecording(details);
})

View File

@ -28,6 +28,7 @@
var $downloadApplicationLink = null;
var $noPurchasesPrompt = null;
var shoppingCartItem = null;
var $startUsingJtPopup = null;
function beforeShow() {
@ -156,6 +157,12 @@
}
})
}
console.log("jamtracks", jamTracks)
$startUsingJtPopup.attr('href', '/popups/jamtrack-player/' + jamTracks[0].id)
$startUsingJtPopup.find('.download-jamkazam').html('Click Here to Start Using <b>' + jamTracks[0].name + '</b>')
context.JK.popExternalLinks($startUsingJtPopup.parent())
}
}
@ -246,6 +253,7 @@
$backBtn = $screen.find('.back');
$downloadApplicationLink = $screen.find('.download-jamkazam-wrapper');
$noPurchasesPrompt = $screen.find('.no-purchases-prompt')
$startUsingJtPopup = $screen.find('.jt-popup')
if ($screen.length == 0) throw "$screen must be specified";

View File

@ -9,7 +9,7 @@
//= require jquery.queryparams
//= require jquery.hoverIntent
//= require jquery.cookie
//= require jquery.clipboard
//= require clipboard
//= require jquery.easydropdown
//= require jquery.carousel-1.1
//= require jquery.mousewheel-3.1.9

View File

@ -867,6 +867,8 @@
else {
var inputSampleRate = 44100;
}
// ignore all this; just set it to
inputSampleRate = 'DEVICE_DEFAULT'
logger.debug("applying the sample rate based on input device: " + selectedDeviceInfo.input.id + " (" + inputSampleRate + ")");
sampleRate.selectSampleRate(inputSampleRate);
context.jamClient.FTUESetPreferredMixerSampleRate(sampleRate.selectedSampleRate());

View File

@ -46,6 +46,9 @@
else if(value == 96000) {
setter = 'PREFER_96'
}
else if (value == 'DEVICE_DEFAULT') {
setter = 'USE_DEVICE_DEFAULT_SR'
}
console.log("SELECT SAMPLE RATE" + value, setter);
context.JK.dropdown($sampleRate.val(setter).easyDropDown('select', setter.toString(), true))
}
@ -63,7 +66,7 @@
}
function resetValues() {
$sampleRate.val('PREFER_44').easyDropDown('select', 'PREFER_44', true)
$sampleRate.val('USE_DEVICE_DEFAULT_SR').easyDropDown('select', 'USE_DEVICE_DEFAULT_SR', true)
}

View File

@ -33,6 +33,24 @@
}
}
.paypal-region {
text-align: center;
margin:10px auto 0;
/**margin: 10px auto 0;
padding: 10px 10px 5px;
background-color: white;
border-radius: 8px;
border-color: #ccc;
border-style: solid;
border-width: 3px;
width:145px;*/
}
.or-text {
margin: 60px auto 0;
text-align:center;
}
h2 {
color:white;
background-color:#4d4d4d;

View File

@ -266,9 +266,19 @@
margin-top: 5px;
}
.jamtrack-variant-help {
margin-bottom:20px;
}
.jamtrack-add-zone {
margin: 8px 0px;
position:relative;
}
.jamtrack-price {
margin-top: 5px;
width:100%;
margin: 0 auto 10px;
font-size: 20px;
color:white;
display:block;
&.free {
margin-top:0;
@ -282,7 +292,7 @@
}
.jamtrack-add-cart, .jamtrack-add-cart-disabled {
margin: 8px 0px;
position:relative;
}
.jamtrack-license {

View File

@ -46,6 +46,9 @@
.scooter {
margin-bottom:10px;
}
div.retailer-split {
margin-top:10px;
}
.store-header {
float: left;
padding-top: 10px;
@ -280,4 +283,10 @@
font-size:12px;
margin:0;
}
.split-input {
:after {
content: '%';
}
}
}

View File

@ -0,0 +1,78 @@
@import "client/common.scss";
[data-react-class="PayPalConfirmationScreen"] {
height: 100%;
overflow: scroll;
.content-body-scroller {
height: calc(100% - 30px) ! important; // 15px top and bottom padding, and 48px used by .controls
padding: 15px 30px;
}
.confirm-header {
color: white;
font-size: 20px;
}
.controls.bottom {
margin-top: 20px;
}
.place-order-btn {
text-align: center;
margin-right:0;
}
.or-holder {
margin-top: 20px;
text-align: center;
}
.cancel-order-btn {
margin-top: 20px;
text-align: center;
}
.shopping-cart-contents {
@include border-box_sizing;
width: 50%;
margin-top:20px;
}
.controls {
@include border-box_sizing;
width:50%;
a {
float:right;
}
}
.loading-indicator {
margin-bottom:20px;
padding-bottom:20px;
}
.sold-notice {
h2 {
font-size:30px;
text-align:center;
}
}
.download-jamkazam {
color:$ColorLink;
border-radius: 4px;
border-style:solid;
border-color:#AAA;
border-width:1px;
padding:10px;
margin-top:20px;
display:inline-block;
}
.download-jamkazam-wrapper, .back-to-browsing {
text-align:center;
display:block;
margin-top:35px;
&.hidden {
display:none;
}
}
}

View File

@ -0,0 +1,64 @@
@import "client/common.scss";
.shopping-cart-contents {
background-color:#262626;
border-width:0 1px 0 0;
border-style:solid;
border-color:#333;
padding:20px 20px 0;
.cart-item-caption {
width: 50%;
text-align: left;
float: left;
margin-bottom: 10px;
@include border_box_sizing;
}
.first-one-free {
font-size: 14px;
font-style: italic;
margin-left: 15px;
}
.cart-item-price {
width: 25%;
text-align: right;
float: left;
padding: 0 10px;
margin-bottom: 10px;
@include border_box_sizing;
}
.cart-item-quantity {
width: 10%;
text-align: right;
float: left;
padding: 0 10px;
margin-bottom: 10px;
@include border_box_sizing;
}
.cart-items {
margin-top: 10px;
padding-left: 10px;
}
.cart-item {
margin-top: 10px;
}
.no-cart-items {
}
.tax-total {
margin-top:10px;
border-width:1px 0 0 0;
border-color:white;
border-style:solid;
padding-top:10px;
}
.cart-item.total {
margin-top:5px;
}
}

View File

@ -199,6 +199,9 @@
position:absolute;
}
.copy-profile-link {
float:right;
}
.spinner-large {
width:200px;

View File

@ -723,7 +723,7 @@
}
.easydropdown-wrapper {
width:auto;
float:right;
float:left;
}
}

View File

@ -12,4 +12,6 @@
*= require dialogs/dialog
*= require icheck/minimal/minimal
*= require landings/posa_activation
*= require landings/simple_jamtracks
*= require landings/simple_jamclass
*/

Some files were not shown because too many files have changed in this diff Show More