merging feature/musician_profile_enhancements

This commit is contained in:
Jonathan Kolyer 2015-03-30 06:07:43 +00:00
commit ddc4decfd3
179 changed files with 6340 additions and 1459 deletions

View File

@ -64,7 +64,7 @@ ActiveAdmin.register JamRuby::JamTrackRight, :as => 'JamTrackRights' do
begin
client.find_or_create_account(user, billing_info)
client.place_order(user, jam_track)
client.place_order(user, jam_track, nil)
rescue RecurlyClientError=>x
redirect_to admin_jam_track_rights_path, notice: "Could not order #{jam_track} for #{user.to_s}: #{x.errors.inspect}"
else

View File

@ -24,6 +24,7 @@ ActiveAdmin.register JamRuby::JamTrack, :as => 'JamTracks' do
column :original_artist
column :name
column :flags do |jam_track| jam_track.duplicate_positions? ? 'DUP POSITIONS' : '' end
column :status
column :master_track do |jam_track| jam_track.master_track.nil? ? 'None' : (link_to "Download", jam_track.master_track.url_by_sample_rate(44)) end
column :licensor

View File

@ -149,9 +149,9 @@ SELECT played.player_id FROM
(SELECT player_id, COUNT(*) cnt FROM playable_plays pp
WHERE
pp.created_at >= '#{start_date}' AND
pp.created_at <= '#{end_date}' AND
pp.playable_type = 'JamRuby::JamTrack'
GROUP BY player_id
pp.created_at <= '#{end_date}' AND
pp.playable_type = 'JamRuby::JamTrack' /* VRFS-2916 jam_tracks.id is varchar: ADD */
GROUP BY player_id
) played
WHERE #{where}
SQL
@ -168,7 +168,10 @@ WHERE
tt.created_at >= '#{start_date}' AND
tt.created_at <= '#{end_date}'
SQL
yield(sql) if block_given?
if block_given?
yield_sql = yield(sql)
sql = yield_sql unless yield_sql.blank?
end
self.class.cohort_users(self).where("users.id IN (#{sql})").count
end
@ -196,12 +199,12 @@ SQL
_put_data_set(assoc_key, count, num_user)
count = _subquery(assoc_key = :jam_track_rights, num_user) do |subsql|
subsql += " AND tt.redeemed = 'f' "
subsql += " AND tt.is_test_purchase = 'f' AND tt.redeemed = 'f' "
end
_put_data_set(assoc_key, count, num_user)
count = _subquery(assoc_key = :jam_track_rights, num_user) do |subsql|
subsql += " AND tt.redeemed = 't' "
subsql += " AND tt.is_test_purchase = 'f' AND tt.redeemed = 't' "
end
_put_data_set(:jam_track_rights_redeemed, count, num_user)
@ -235,13 +238,6 @@ SQL
self.save!
end
def _join_user_all_time(assoc_ref)
assoc_ref.active_record
.joins("INNER JOIN users AS uu ON uu.id = #{assoc_ref.foreign_key}")
.where(created_at: self.group_start..self.group_end)
.where(['uu.created_at >= ? AND uu.created_at <= ?', self.group_start, self.group_end])
end
def _all_time!
unless 0 < num_user = self.class.cohort_users(self).count
self.update_attribute(:data_set, {})
@ -270,10 +266,15 @@ SQL
count = _subquery(assoc_key = :friendships, num_user)
_put_data_set(assoc_key, count, num_user)
count = _subquery(assoc_key = :jam_track_rights, num_user)
count = _subquery(assoc_key = :jam_track_rights, num_user) do |subsql|
subsql += " AND tt.is_test_purchase = 'f'"
end
_put_data_set(assoc_key, count, num_user)
count = _subquery(assoc_key = :jam_tracks_played, num_user) do |subsql|
# VRFS-2916 jam_tracks.id is varchar: REMOVE
# subsql += " AND tt.jam_track_id IS NOT NULL "
# VRFS-2916 jam_tracks.id is varchar: ADD
subsql += " AND tt.playable_type = 'JamRuby::JamTrack' "
end
_put_data_set(assoc_key, count, num_user)

View File

@ -0,0 +1,5 @@
= f.inputs name: 'Jam Track Right fields' do
ol.nested-fields
= f.input :jam_track, :required=>true, collection: JamTrack.all, include_blank: false
= f.input :user, :required=>true, collection: User.all, include_blank: false

View File

@ -68,7 +68,7 @@ class JamRuby::JamTrackTrack
s3_manager.download(self.url_by_sample_rate(44), input)
command = "sox \"#{input}\" \"#{output}\" trim #{start} #{stop}"
command = "sox \"#{input}\" \"#{output}\" trim #{sprintf("%.3f", start)} =#{sprintf("%.3f", stop)}"
@@log.debug("trimming using: " + command)

View File

@ -11,6 +11,7 @@ FactoryGirl.define do
state "NC"
country "US"
terms_of_service true
reuse_card true
factory :admin do

View File

@ -270,4 +270,10 @@ preview_jam_track_tracks.sql
cohorts.sql
jam_track_right_admin_purchase.sql
alter_genre_player_unique_constraint.sql
jam_track_playable_plays.sql
shopping_cart_anonymous.sql
user_reuse_card_and_reedem.sql
jam_track_id_to_varchar.sql
drop_position_unique_jam_track.sql
recording_client_metadata.sql
musician_search.sql

View File

@ -0,0 +1 @@
DROP INDEX jam_track_tracks_position_uniqkey;

View File

@ -0,0 +1,25 @@
-- change jam_tracks PRIMARY KEY to VARCHAR(64)
-- first, drop all constraints and change the types
ALTER TABLE jam_track_tracks DROP CONSTRAINT jam_track_tracks_jam_track_id_fkey;
ALTER TABLE jam_track_tracks ALTER COLUMN jam_track_id TYPE VARCHAR(64);
ALTER TABLE jam_track_tap_ins DROP CONSTRAINT jam_track_tap_ins_jam_track_id_fkey;
ALTER TABLE jam_track_tap_ins ALTER COLUMN jam_track_id TYPE VARCHAR(64);
ALTER TABLE jam_track_rights DROP CONSTRAINT jam_track_rights_jam_track_id_fkey;
ALTER TABLE jam_track_rights ALTER COLUMN jam_track_id TYPE VARCHAR(64);
ALTER TABLE active_music_sessions ALTER COLUMN jam_track_id TYPE VARCHAR(64);
ALTER TABLE recordings DROP CONSTRAINT recordings_jam_track_id_fkey;
ALTER TABLE recordings ALTER COLUMN jam_track_id TYPE VARCHAR(64);
ALTER TABLE playable_plays DROP COLUMN jam_track_id;
-- then drop the jamtrack sequence, change it's type, and then set default to UUID
-- DROP SEQUENCE jam_tracks_next_seq;
ALTER TABLE jam_tracks ALTER COLUMN id TYPE VARCHAR(64);
ALTER TABLE jam_tracks ALTER COLUMN id SET DEFAULT uuid_generate_v4();
-- add back in all the constraints on the fk tables
ALTER TABLE jam_track_tracks ADD CONSTRAINT jam_track_tracks_jam_track_id_fkey FOREIGN KEY (jam_track_id) REFERENCES jam_tracks(id) ON DELETE CASCADE;
ALTER TABLE jam_track_tap_ins ADD CONSTRAINT jam_track_tap_ins_jam_track_id_fkey FOREIGN KEY (jam_track_id) REFERENCES jam_tracks(id) ON DELETE CASCADE;
ALTER TABLE jam_track_rights ADD CONSTRAINT jam_track_rights_jam_track_id_fkey FOREIGN KEY (jam_track_id) REFERENCES jam_tracks(id);
ALTER TABLE recordings ADD CONSTRAINT recordings_jam_track_id_fkey FOREIGN KEY (jam_track_id) REFERENCES jam_tracks(id);

View File

@ -0,0 +1,6 @@
ALTER TABLE playable_plays ADD COLUMN jam_track_id bigint;
ALTER TABLE playable_plays ALTER COLUMN playable_id DROP NOT NULL;
ALTER TABLE playable_plays ALTER COLUMN playable_type DROP NOT NULL;

View File

@ -0,0 +1 @@
ALTER TABLE recordings ADD COLUMN timeline JSON;

View File

@ -0,0 +1,2 @@
ALTER TABLE shopping_carts ALTER COLUMN user_id DROP NOT NULL;
ALTER TABLE shopping_carts ADD COLUMN anonymous_user_id VARCHAR(1000);

View File

@ -0,0 +1,3 @@
ALTER TABLE users ADD COLUMN reuse_card BOOLEAN DEFAULT TRUE NOT NULL;
ALTER TABLE users ADD COLUMN has_redeemable_jamtrack BOOLEAN DEFAULT TRUE NOT NULL;
ALTER TABLE shopping_carts ADD COLUMN marked_for_redeem INTEGER DEFAULT 0 NOT NULL;

View File

@ -7,5 +7,3 @@ Create development database 'jam_ruby'
Once you've created your database, migrate it:
`bundle exec jam_ruby up`

View File

@ -97,6 +97,7 @@ require "jam_ruby/models/max_mind_release"
require "jam_ruby/models/genre_player"
require "jam_ruby/models/genre"
require "jam_ruby/models/user"
require "jam_ruby/models/anonymous_user"
require "jam_ruby/models/rsvp_request"
require "jam_ruby/models/rsvp_slot"
require "jam_ruby/models/rsvp_request_rsvp_slot"

View File

@ -15,6 +15,7 @@ module JamRuby
class << self
def save_jam_track_jkz(user, jam_track, sample_rate=48)
jam_track_right = jam_track.right_for_user(user)
raise ArgumentError if jam_track_right.nil?

View File

@ -0,0 +1,29 @@
# this was added to support the idea of an anonymous user interacting with our site; needed by the ShoppingCart
# over time it might make sense to beef this up and to use it conistently in anonymous interactions
module JamRuby
class AnonymousUser
attr_accessor :id
def initialize(id)
@id = id
end
def shopping_carts
ShoppingCart.where(anonymous_user_id: @id)
end
def destroy_all_shopping_carts
ShoppingCart.destroy_all(anonymous_user_id: @id)
end
def admin
false
end
def has_redeemable_jamtrack
true
end
end
end

View File

@ -44,25 +44,73 @@ module JamRuby
belongs_to :genre, class_name: "JamRuby::Genre"
belongs_to :licensor , class_name: 'JamRuby::JamTrackLicensor', foreign_key: 'licensor_id'
has_many :jam_track_tracks, :class_name => "JamRuby::JamTrackTrack", order: 'position ASC'
has_many :jam_track_tracks, :class_name => "JamRuby::JamTrackTrack", order: 'position ASC, part ASC, instrument_id ASC'
has_many :jam_track_tap_ins, :class_name => "JamRuby::JamTrackTapIn", order: 'offset_time ASC'
has_many :jam_track_rights, :class_name => "JamRuby::JamTrackRight" #, inverse_of: 'jam_track', :foreign_key => "jam_track_id"
has_many :jam_track_rights, :class_name => "JamRuby::JamTrackRight" #, inverse_of: 'jam_track', :foreign_key => "jam_track_id" # '
has_many :owners, :through => :jam_track_rights, :class_name => "JamRuby::User", :source => :user
has_many :playing_sessions, :class_name => "JamRuby::ActiveMusicSession"
has_many :recordings, :class_name => "JamRuby::Recording"
# VRFS-2916 jam_tracks.id is varchar: REMOVE
# has_many :plays, :class_name => "JamRuby::PlayablePlay", :foreign_key => :jam_track_id, :dependent => :destroy
# VRFS-2916 jam_tracks.id is varchar: ADD
has_many :plays, :class_name => "JamRuby::PlayablePlay", :as => :playable, :dependent => :destroy
accepts_nested_attributes_for :jam_track_tracks, allow_destroy: true
accepts_nested_attributes_for :jam_track_tap_ins, allow_destroy: true
def duplicate_positions?
counter = {}
jam_track_tracks.each do |track|
count = counter[track.position]
if count.nil?
count = 0
end
puts "count #{count}"
counter[track.position] = count + 1
end
duplicate = false
counter.each do|position, count|
if count > 1
duplicate = true
break
end
end
duplicate
end
class << self
# @return array[artist_name(string)]
def all_artists
JamTrack.select("original_artist").
group("original_artist").
collect{|jam_track|jam_track.original_artist}
end
# @return array[JamTrack] for given artist_name
def tracks_for_artist(artist_name)
JamTrack.where("original_artist=?", artist_name).all
end
def index(options, user)
if options[:page]
page = options[:page].to_i
per_page = options[:per_page].to_i
if per_page == 0
# try and see if limit was specified
limit = options[:limit]
limit ||= 20
limit = limit.to_i
else
limit = per_page
end
start = (page -1 )* per_page
limit = per_page
else
@ -85,6 +133,14 @@ module JamRuby
query = query.joins(:jam_track_rights)
query = query.where("jam_track_rights.user_id = ?", user.id)
end
if options[:artist].present?
query = query.where("original_artist=?", options[:artist])
end
if options[:group_artist]
query = query.group("original_artist")
end
query = query.where("jam_tracks.status = ?", 'Production') unless user.admin
query = query.where("jam_tracks.genre_id = '#{options[:genre]}'") unless options[:genre].blank?
@ -114,6 +170,6 @@ module JamRuby
def right_for_user(user)
jam_track_rights.where("user_id=?", user).first
end
end
end
end

View File

@ -101,6 +101,7 @@ module JamRuby
remove_url_44!
end
def enqueue(sample_rate=48)
begin
JamTrackRight.where(:id => self.id).update_all(:signing_queued_at => Time.now, :signing_started_at => nil, :last_signed_at => nil)
@ -124,6 +125,7 @@ module JamRuby
end
end
# @return true if signed && file exists for the sample_rate specifed:
def ready?(sample_rate=48)
if sample_rate==48

View File

@ -23,7 +23,6 @@ module JamRuby
validates :part, length: {maximum: 25}
validates :track_type, inclusion: {in: TRACK_TYPE }
validates :preview_start_time, numericality: {only_integer: true}, length: {in: 1..1000}, :allow_nil => true
validates_uniqueness_of :position, scope: :jam_track_id
validates_uniqueness_of :part, scope: [:jam_track_id, :instrument_id]
# validates :jam_track, presence: true

View File

@ -136,23 +136,43 @@ module JamRuby
def manifest
one_day = 60 * 60 * 24
jam_track_offset = 0
if recording.timeline
recording_timeline_data = JSON.parse(recording.timeline)
recording_start_time = recording_timeline_data["recording_start_time"]
jam_track_play_start_time = recording_timeline_data["jam_track_play_start_time"]
jam_track_recording_start_play_offset = recording_timeline_data["jam_track_recording_start_play_offset"]
jam_track_offset = -jam_track_recording_start_play_offset
end
manifest = { "files" => [], "timeline" => [] }
mix_params = []
# this 'pick limiter' logic will ensure that we set a limiter on the 1st recorded_track we come across.
pick_limiter = false
if recording.is_jamtrack_recording?
# we only use the limiter feature if this is a JamTrack recording
# by setting this to true, the 1st recorded_track in the database will be the limiter
pick_limiter = true
end
recording.recorded_tracks.each do |recorded_track|
manifest["files"] << { "filename" => recorded_track.sign_url(one_day), "codec" => "vorbis", "offset" => 0 }
mix_params << { "level" => 100, "balance" => 0 }
# change to 1.0 level later
manifest["files"] << { "filename" => recorded_track.sign_url(one_day), "codec" => "vorbis", "offset" => 0, limiter:pick_limiter }
pick_limiter = false
mix_params << { "level" => 1.0, "balance" => 0 }
end
recording.recorded_backing_tracks.each do |recorded_backing_track|
manifest["files"] << { "filename" => recorded_backing_track.sign_url(one_day), "codec" => "vorbis", "offset" => 0 }
mix_params << { "level" => 100, "balance" => 0 }
# change to 1.0 level later
mix_params << { "level" => 1.0, "balance" => 0 }
end
recording.recorded_jam_track_tracks.each do |recorded_jam_track_track|
manifest["files"] << { "filename" => recorded_jam_track_track.jam_track_track.sign_url(one_day), "codec" => "vorbis", "offset" => 0 }
manifest["files"] << { "filename" => recorded_jam_track_track.jam_track_track.sign_url(one_day), "codec" => "vorbis", "offset" => jam_track_offset }
# let's look for level info from the client
level = 1.0 # default value - means no effect
if recorded_jam_track_track.timeline

View File

@ -307,7 +307,7 @@ module JamRuby
filter_approved = only_approved ? 'AND rrrs.chosen = true' : ''
MusicSession.where(%Q{music_sessions.canceled = FALSE AND
music_sessions.create_type != '#{CREATE_TYPE_QUICK_START}' AND
(music_sessions.create_type is NULL OR music_sessions.create_type != '#{CREATE_TYPE_QUICK_START}') AND
(music_sessions.scheduled_start is NULL OR music_sessions.scheduled_start > NOW() - '4 hour'::INTERVAL) AND
music_sessions.id in (
select distinct(rs.music_session_id)

View File

@ -2,9 +2,27 @@ module JamRuby
class PlayablePlay < ActiveRecord::Base
self.table_name = "playable_plays"
belongs_to :playable, :polymorphic => :true, :counter_cache => :play_count
belongs_to :playable, :polymorphic => :true
# VRFS-2916 jam_tracks.id is varchar: REMOVE
#belongs_to :jam_track, :foreign_key => :jam_track_id
belongs_to :user, :class_name => "JamRuby::User", :foreign_key => "player_id"
belongs_to :claimed_recording, :class_name => "JamRuby::ClaimedRecording", :foreign_key => "claimed_recording_id"
validate do
# VRFS-2916 jam_tracks.id is varchar: REMOVE
#if !playable_id && !jam_track_id
# self.errors[:base] << 'No playable instance detected'
#end
# VRFS-2916 jam_tracks.id is varchar: ADD
if !playable_id
self.errors[:base] << 'No playable instance detected'
end
if !user
self.errors[:base] << 'No user detected'
end
end
end
end

View File

@ -9,10 +9,11 @@ module JamRuby
validates :user, presence: true
validates :jam_track_track, presence:true
def self.create_from_jam_track_track(jam_track_track, recording)
def self.create_from_jam_track_track(jam_track_track, recording, user)
recorded_jam_track_track = self.new
recorded_jam_track_track.recording = recording
recorded_jam_track_track.jam_track_track = jam_track_track
recorded_jam_track_track.user = user
recorded_jam_track_track.save
recorded_jam_track_track
end

View File

@ -49,6 +49,10 @@ module JamRuby
self.comments.size
end
def is_jamtrack_recording?
!jam_track_id.nil?
end
def high_quality_mix?
has_final_mix
end
@ -229,11 +233,13 @@ module JamRuby
if music_session.jam_track
music_session.jam_track.jam_track_tracks.each do |jam_track_track|
recording.recorded_jam_track_tracks << RecordedJamTrackTrack.create_from_jam_track_track(jam_track_track, recording)
recording.recorded_jam_track_tracks << RecordedJamTrackTrack.create_from_jam_track_track(jam_track_track, recording, owner) if jam_track_track.track_type == 'Track'
end
recording.jam_track = music_session.jam_track
recording.jam_track_initiator = music_session.jam_track_initiator
end
recording.save
end
end
@ -690,19 +696,17 @@ module JamRuby
end
def add_timeline(timeline)
tracks = timeline["tracks"]
global = timeline["global"]
raise JamArgumentError, "global must be specified" unless global
tracks = timeline["tracks"]
raise JamArgumentError, "tracks must be specified" unless tracks
Recording.where(id: self.id).update_all(timeline: global.to_json)
jam_tracks = tracks.select {|track| track["type"] == "jam_track"}
jam_tracks.each do |client_jam_track|
recorded_jam_track_track = RecordedJamTrackTrack.find_by_jam_track_track_id(client_jam_track["id"])
if recorded_jam_track_track
recorded_jam_track_track.timeline = client_jam_track["timeline"].to_json
recorded_jam_track_track.save!
else
@@log.error("unable to find JamTrackTrack with id #{recorded_jam_track_track.id}")
end
RecordedJamTrackTrack.where(recording_id: id, jam_track_track_id: client_jam_track["id"]).update_all(timeline: client_jam_track["timeline"].to_json)
end
end

View File

@ -8,27 +8,105 @@ module JamRuby
validates :cart_id, presence: true
validates :cart_type, presence: true
validates :cart_class_name, presence: true
validates :marked_for_redeem, numericality: {only_integer: true}
default_scope order('created_at DESC')
def product_info
product = self.cart_product
{name: product.name, price: product.price, product_id: cart_id} unless product.nil?
{name: product.name, price: product.price, product_id: cart_id, plan_code: product.plan_code, total_price: total_price(product), quantity: quantity, marked_for_redeem: marked_for_redeem} unless product.nil?
end
# multiply (quantity - redeemable) by price
def total_price(product)
(quantity - marked_for_redeem) * product.price
end
def cart_product
self.cart_class_name.classify.constantize.find_by_id self.cart_id unless self.cart_class_name.blank?
self.cart_class_name.classify.constantize.find_by_id(self.cart_id) unless self.cart_class_name.blank?
end
def self.create user, product, quantity = 1
def redeem(mark_redeem)
self.marked_for_redeem = mark_redeem ? 1 : 0
end
def free?
marked_for_redeem == quantity
end
def self.create user, product, quantity = 1, mark_redeem = false
cart = ShoppingCart.new
cart.user = user
if user.is_a?(User)
cart.user = user
else
cart.anonymous_user_id = user.id
end
cart.cart_type = product.class::PRODUCT_TYPE
cart.cart_class_name = product.class.name
cart.cart_id = product.id
cart.quantity = quantity
cart.redeem(mark_redeem)
cart.save
cart
end
# if the user has a redeemable jam_track still on their account, then also check if any shopping carts have already been marked.
# if no shpping carts have been marked, then mark it redeemable
# should be wrapped in a TRANSACTION
def self.user_has_redeemable_jam_track?(any_user)
mark_redeem = false
if APP_CONFIG.one_free_jamtrack_per_user && any_user.has_redeemable_jamtrack
mark_redeem = true # start out assuming we can redeem...
any_user.shopping_carts.each do |shopping_cart|
# but if we find any shopping cart item already marked for redeem, then back out of mark_redeem=true
if shopping_cart.cart_type == JamTrack::PRODUCT_TYPE && shopping_cart.marked_for_redeem > 0
mark_redeem = false
break
end
end
end
mark_redeem
end
# adds a jam_track to cart, checking for promotions
def self.add_jam_track_to_cart(any_user, jam_track)
cart = nil
ShoppingCart.transaction do
# does this user already have this JamTrack in their cart? If so, don't add it.
duplicate_found = false
any_user.shopping_carts.each do |shopping_cart|
if shopping_cart.cart_type == JamTrack::PRODUCT_TYPE && shopping_cart.cart_id == jam_track.id
duplicate_found = true
return
end
end
unless duplicate_found
mark_redeem = ShoppingCart.user_has_redeemable_jam_track?(any_user)
cart = ShoppingCart.create(any_user, jam_track, 1, mark_redeem)
end
end
cart
end
# deletes a jam track from the shopping cart, updating redeem flag as necessary
def self.remove_jam_track_from_cart(any_user, cart)
ShoppingCart.transaction do
cart.destroy
# check if we should move the redemption
mark_redeem = ShoppingCart.user_has_redeemable_jam_track?(any_user)
carts = any_user.shopping_carts
# if we find any carts on the account, mark one redeemable
if mark_redeem && carts.length > 0
carts[0].redeem(mark_redeem)
carts[0].save
end
end
end
end
end

View File

@ -71,6 +71,11 @@ module JamRuby
has_many :playing_claimed_recordings, :class_name => "JamRuby::ActiveMusicSession", :inverse_of => :claimed_recording_initiator
has_many :playing_jam_tracks, :class_name => "JamRuby::ActiveMusicSession", :inverse_of => :jam_track_initiator
# VRFS-2916 jam_tracks.id is varchar: REMOVE
# has_many :jam_tracks_played, :class_name => "JamRuby::PlayablePlay", :foreign_key => 'player_id', :conditions => "jam_track_id IS NOT NULL"
# VRFS-2916 jam_tracks.id is varchar: ADD
has_many :jam_tracks_played, :class_name => "JamRuby::PlayablePlay", :foreign_key => 'player_id', :conditions => ["playable_type = 'JamRuby::JamTrack'"]
# self.id = user_id in likes table
has_many :likings, :class_name => "JamRuby::Like", :inverse_of => :user, :dependent => :destroy
@ -176,6 +181,8 @@ module JamRuby
validates_confirmation_of :password, :if => :should_validate_password?
validates :terms_of_service, :acceptance => {:accept => true, :on => :create, :allow_nil => false }
validates :reuse_card, :inclusion => {:in => [true, false]}
validates :has_redeemable_jamtrack, :inclusion => {:in => [true, false]}
validates :subscribe_email, :inclusion => {:in => [nil, true, false]}
validates :musician, :inclusion => {:in => [true, false]}
validates :show_whats_next, :inclusion => {:in => [nil, true, false]}
@ -366,6 +373,10 @@ module JamRuby
MusicSession.scheduled_rsvp(self, true).length
end
def purchased_jamtracks_count
self.purchased_jam_tracks.count
end
def joined_score
return nil unless has_attribute?(:score)
a = read_attribute(:score)
@ -961,6 +972,8 @@ module JamRuby
signup_confirm_url = options[:signup_confirm_url]
affiliate_referral_id = options[:affiliate_referral_id]
recaptcha_failed = options[:recaptcha_failed]
any_user = options[:any_user]
reuse_card = options[:reuse_card]
user = User.new
@ -971,6 +984,7 @@ module JamRuby
user.subscribe_email = true
user.terms_of_service = terms_of_service
user.musician = musician
user.reuse_card unless reuse_card.nil?
# FIXME: Setting random password for social network logins. This
# is because we have validations all over the place on this.
@ -1015,6 +1029,9 @@ module JamRuby
user.photo_url = photo_url
# copy over the shopping cart to the new user, if a shopping cart is provided
user.shopping_carts = any_user.shopping_carts if any_user
unless fb_signup.nil?
user.update_fb_authorization(fb_signup)
@ -1536,6 +1553,11 @@ module JamRuby
stats['audio_latency_avg'] = result['last_jam_audio_latency_avg'].to_f
stats
end
def destroy_all_shopping_carts
ShoppingCart.where("user_id=?", self).destroy_all
end
private
def create_remember_token
self.remember_token = SecureRandom.urlsafe_base64

View File

@ -4,7 +4,7 @@ module JamRuby
def initialize()
end
def create_account(current_user, billing_info=nil)
def create_account(current_user, billing_info)
options = account_hash(current_user, billing_info)
account = nil
begin
@ -12,7 +12,7 @@ module JamRuby
account = Recurly::Account.create(options)
raise RecurlyClientError.new(account.errors) if account.errors.any?
rescue Recurly::Error, NoMethodError => x
puts "Error: #{x} : #{Kernel.caller}"
#puts "Error: #{x} : #{Kernel.caller}"
raise RecurlyClientError, x.to_s
else
if account
@ -37,7 +37,9 @@ module JamRuby
end
def get_account(current_user)
(current_user && current_user.recurly_code) ? Recurly::Account.find(current_user.recurly_code) : nil
current_user && current_user.recurly_code ? Recurly::Account.find(current_user.recurly_code) : nil
rescue Recurly::Error => x
raise RecurlyClientError, x.to_s
end
def update_account(current_user, billing_info=nil)
@ -53,12 +55,35 @@ module JamRuby
account
end
def payment_history(current_user)
payments = []
account = get_account(current_user)
if(account.present?)
begin
account.transactions.find_each do |transaction|
if transaction.amount_in_cents > 0 # Account creation adds a transaction record
payments << {
:created_at => transaction.created_at,
:amount_in_cents => transaction.amount_in_cents,
:status => transaction.status,
:payment_method => transaction.payment_method,
:reference => transaction.reference
}
end
end
rescue Recurly::Error, NoMethodError => x
raise RecurlyClientError, x.to_s
end
end
payments
end
def update_billing_info(current_user, billing_info=nil)
account = get_account(current_user)
if (account.present?)
begin
account.billing_info=billing_info
account.billing_info.save
account.billing_info = billing_info
account.billing_info.save
rescue Recurly::Error, NoMethodError => x
raise RecurlyClientError, x.to_s
end
@ -145,7 +170,7 @@ module JamRuby
raise RecurlyClientError.new(plan.errors) if plan.errors.any?
end
def place_order(current_user, jam_track)
def place_order(current_user, jam_track, shopping_cart)
jam_track_right = nil
account = get_account(current_user)
if (account.present?)
@ -160,13 +185,22 @@ module JamRuby
end
end
free = false
# this means we already have a subscription, so don't try to create a new one for the same plan (Recurly would fail this anyway)
unless recurly_subscription_uuid
subscription = Recurly::Subscription.create(:account=>account, :plan_code=>jam_track.plan_code)
# if the shopping cart was specified, see if the item should be free
free = shopping_cart.nil? ? false : shopping_cart.free?
# and if it's free, squish the charge to 0.
unit_amount_in_cents = free ? 0 : nil
subscription = Recurly::Subscription.create(:account=>account, :plan_code=>jam_track.plan_code, unit_amount_in_cents: unit_amount_in_cents)
raise RecurlyClientError.new(subscription.errors) if subscription.errors.any?
# delete from shopping cart the subscription
shopping_cart.destroy if shopping_cart
# Reload and make sure it went through:
account = get_account(current_user)
@ -180,11 +214,19 @@ module JamRuby
raise RecurlyClientError, "Plan code '#{paid_subscription.plan_code}' doesn't match jam track: '#{jam_track.plan_code}'" unless recurly_subscription_uuid
jam_track_right=JamRuby::JamTrackRight.find_or_create_by_user_id_and_jam_track_id(current_user.id, jam_track.id)
jam_track_right = JamRuby::JamTrackRight.find_or_create_by_user_id_and_jam_track_id(current_user.id, jam_track.id) do |jam_track_right|
jam_track_right.redeemed = free
end
User.where(id: current_user.id).update_all(has_redeemable_jamtrack: false) if free
# also if the purchase was a free one, then update the user record to no longer allow redeemed jamtracks
# this can't go in the block above, as it's here to fix bad subscription UUIDs in an update path
if jam_track_right.recurly_subscription_uuid != recurly_subscription_uuid
jam_track_right.recurly_subscription_uuid = recurly_subscription_uuid
jam_track_right.save
end
raise RecurlyClientError.new("Error creating jam_track_right for jam_track: #{jam_track.id}") if jam_track_right.nil?
raise RecurlyClientError.new(jam_track_right.errors) if jam_track_right.errors.any?
rescue Recurly::Error, NoMethodError => x
@ -198,7 +240,7 @@ module JamRuby
jam_track_right
end
def find_or_create_account(current_user, billing_info=nil)
def find_or_create_account(current_user, billing_info)
account = get_account(current_user)
if(account.nil?)

View File

@ -260,6 +260,10 @@ module JamRuby
end
@manifest = symbolize_keys(mix.manifest)
@@log.debug("manifest")
@@log.debug("--------")
@@log.debug(JSON.pretty_generate(@manifest))
@manifest[:mix_id] = mix_id # slip in the mix_id so that the job can add it to the ogg comments
# sanity check the manifest

View File

@ -18,6 +18,7 @@ FactoryGirl.define do
musician true
terms_of_service true
last_jam_audio_latency 5
reuse_card true
#u.association :musician_instrument, factory: :musician_instrument, user: u
@ -583,6 +584,7 @@ FactoryGirl.define do
end
factory :playable_play, :class => JamRuby::PlayablePlay do
association :user, factory: :user
end
factory :recording_like, :class => JamRuby::RecordingLiker do

View File

@ -64,6 +64,7 @@ describe JamTrackRight do
s3.upload(jam_track_track.manually_uploaded_filename(:url_48), ogg_path)
jam_track_track[:url_48] = jam_track_track.manually_uploaded_filename(:url_48)
jam_track_track.save!
jam_track_track[:url_48].should == jam_track_track.manually_uploaded_filename(:url_48)
# verify it's on S3
@ -97,7 +98,7 @@ describe JamTrackRight do
end
it "bogus key" do
JamTrackRight.list_keys(user, [2112]).should eq([])
JamTrackRight.list_keys(user, ['2112']).should eq([])
end
it "valid track with no rights to it by querying user" do

View File

@ -14,6 +14,30 @@ describe JamTrack do
jam_track.licensor.jam_tracks.should == [jam_track]
end
describe 'plays' do
it "creates played instance properly" do
@jam_track = FactoryGirl.create(:jam_track)
play = PlayablePlay.new
# VRFS-2916 jam_tracks.id is varchar: REMOVE
# play.jam_track = @jam_track
# VRFS-2916 jam_tracks.id is varchar: ADD
play.playable = @jam_track
play.user = user
play.save!
expect(@jam_track.plays.count).to eq(1)
expect(@jam_track.plays[0].user.id).to eq(user.id)
expect(user.jam_tracks_played.count).to eq(1)
end
it "handles played errors" do
play = PlayablePlay.new
play.user = user
play.save
expect(play.errors.count).to eq(1)
end
end
describe "index" do
it "empty query" do
query, pager = JamTrack.index({}, user)

View File

@ -16,7 +16,7 @@ describe JamTrackTrack do
jam_track_track_1 = FactoryGirl.create(:jam_track_track, position: 1, jam_track: jam_track)
jam_track_track_2 = FactoryGirl.build(:jam_track_track, position: 1, jam_track: jam_track)
jam_track_track_2.valid?.should == false
jam_track_track_2.errors[:position].should == ['has already been taken']
#jam_track_track_2.errors[:position].should == ['has already been taken']
end
it "jam_track required" do

View File

@ -854,6 +854,13 @@ describe MusicSession do
music_session_1.rsvp_slots[0].rsvp_requests_rsvp_slots[0].save!
MusicSession.scheduled_rsvp(creator_1, true).should == []
end
it "create_type = nil will still return RSVPs" do
music_session_1.create_type = nil
music_session_1.save!
MusicSession.scheduled_rsvp(creator_1, true).should == [music_session_1]
end
end
end

View File

@ -1080,6 +1080,7 @@ describe Recording do
let(:recording) {recorded_jam_track_track.recording}
let(:timeline_data) {{"sample" => "data"}}
let(:good_timeline) { {
"global" => {"recording_start_time" => 0, "jam_track_play_start_time" => 0, "jam_track_recording_start_play_offset" => 0},
"tracks" => [
{
"id" => recorded_jam_track_track.jam_track_track.id,

View File

@ -4,6 +4,7 @@ describe ShoppingCart do
let(:user) { FactoryGirl.create(:user) }
let(:jam_track) {FactoryGirl.create(:jam_track) }
let(:jam_track2) {FactoryGirl.create(:jam_track) }
before(:each) do
ShoppingCart.delete_all
@ -20,4 +21,48 @@ describe ShoppingCart do
user.shopping_carts[0].quantity.should == 1
end
it "should not add duplicate JamTrack to ShoppingCart" do
cart1 = ShoppingCart.add_jam_track_to_cart(user, jam_track)
cart1.should_not be_nil
user.reload
cart2 = ShoppingCart.add_jam_track_to_cart(user, jam_track)
cart2.should be_nil
end
describe "redeemable behavior" do
it "adds redeemable item to shopping cart" do
user.has_redeemable_jamtrack.should be_true
# first item added to shopping cart should be marked for redemption
cart = ShoppingCart.add_jam_track_to_cart(user, jam_track)
cart.marked_for_redeem.should eq(1)
# but the second item should not
user.reload
cart = ShoppingCart.add_jam_track_to_cart(user, jam_track2)
cart.marked_for_redeem.should eq(0)
end
it "removes redeemable item to shopping cart" do
user.has_redeemable_jamtrack.should be_true
cart1 = ShoppingCart.add_jam_track_to_cart(user, jam_track)
cart1.should_not be_nil
user.reload
cart2 = ShoppingCart.add_jam_track_to_cart(user, jam_track2)
cart2.should_not be_nil
cart1.marked_for_redeem.should eq(1)
cart2.marked_for_redeem.should eq(0)
ShoppingCart.remove_jam_track_from_cart(user, jam_track)
user.shopping_carts.length.should eq(1)
cart2.reload
cart1.marked_for_redeem.should eq(1)
end
end
end

View File

@ -88,21 +88,23 @@ describe RecurlyClient do
end
it "can place order" do
history_items = @client.payment_history(@user).length
@client.find_or_create_account(@user, @billing_info)
expect{@client.place_order(@user, @jamtrack)}.not_to raise_error()
expect{@client.place_order(@user, @jamtrack, nil)}.not_to raise_error()
subs = @client.get_account(@user).subscriptions
subs.should_not be_nil
subs.should have(1).items
@user.jam_track_rights.should_not be_nil
@user.jam_track_rights.should have(1).items
@user.jam_track_rights.last.jam_track.id.should eq(@jamtrack.id)
@client.payment_history(@user).should have(history_items+1).items
end
it "can refund subscription" do
@client.find_or_create_account(@user, @billing_info)
# Place order:
expect{@client.place_order(@user, @jamtrack)}.not_to raise_error()
expect{@client.place_order(@user, @jamtrack, nil)}.not_to raise_error()
active_subs=@client.get_account(@user).subscriptions.find_all{|t|t.state=='active'}
@jamtrack.reload
@jamtrack.jam_track_rights.should have(1).items
@ -118,10 +120,10 @@ describe RecurlyClient do
it "detects error on double order" do
@client.find_or_create_account(@user, @billing_info)
jam_track_right = @client.place_order(@user, @jamtrack)
jam_track_right = @client.place_order(@user, @jamtrack, nil)
jam_track_right.recurly_subscription_uuid.should_not be_nil
jam_track_right2 = @client.place_order(@user, @jamtrack)
jam_track_right2 = @client.place_order(@user, @jamtrack, nil)
jam_track_right.should eq(jam_track_right2)
jam_track_right.recurly_subscription_uuid.should eq(jam_track_right.recurly_subscription_uuid)
end

View File

@ -32,7 +32,7 @@ describe JamTracksBuilder do
jam_track_track.save!
jam_track_track[:url_48].should == jam_track_track.manually_uploaded_filename(:url_48)
# verify it's on S3
@s3.exists?(jam_track_track[:url_48]).should be_true
@s3.length(jam_track_track[:url_48]).should == File.size?(ogg_path)

View File

@ -166,6 +166,10 @@ def app_config
20 # 20 seconds
end
def one_free_jamtrack_per_user
true
end
private

View File

@ -56,6 +56,7 @@ gem 'aasm', '3.0.16'
gem 'carrierwave', '0.9.0'
gem 'carrierwave_direct'
gem 'fog'
gem 'jquery-payment-rails'
gem 'haml-rails'
gem 'unf' #optional fog dependency
gem 'devise', '3.3.0' #3.4.0 causes uninitialized constant ActionController::Metal (NameError)

View File

@ -1,5 +1,5 @@
Jasmine Javascript Unit Tests
=============================
Open browser to localhost:3000/teaspoon

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -33,6 +33,10 @@
}
}
function licenseDetail(userDetail) {
return (userDetail.purchased_jamtracks_count==0) ? "You don't currently own any JamTracks" : 'You currently own a license to use ' + userDetail.purchased_jamtracks_count + " JamTracks"
}
function populateAccount(userDetail) {
var validProfiles = prettyPrintAudioProfiles(context.JK.getGoodConfigMap());
@ -42,8 +46,10 @@
var $template = $(context._.template($('#template-account-main').html(), {
email: userDetail.email,
name: userDetail.name,
licenseDetail: licenseDetail(userDetail),
location : userDetail.location,
session : sessionSummary,
paymentMethod: "mastercard",
instruments : prettyPrintInstruments(userDetail.instruments),
photoUrl : context.JK.resolveAvatarUrl(userDetail.photo_url),
validProfiles : validProfiles,
@ -94,20 +100,28 @@
// events for main screen
function events() {
// wire up main panel clicks
// wire up main panel clicks:
$('#account-content-scroller').on('click', '#account-scheduled-sessions-link', function(evt) { evt.stopPropagation(); navToScheduledSessions(); return false; } );
$('#account-content-scroller').on('click', '#account-my-jamtracks-link', function(evt) { evt.stopPropagation(); navToMyJamTracks(); return false; } );
$('#account-content-scroller').on('click', '#account-edit-identity-link', function(evt) { evt.stopPropagation(); navToEditIdentity(); return false; } );
$('#account-content-scroller').on('click', '#account-edit-profile-link', function(evt) { evt.stopPropagation(); navToEditProfile(); return false; } );
$('#account-content-scroller').on('click', '#account-edit-subscriptions-link', function(evt) { evt.stopPropagation(); navToEditSubscriptions(); return false; } );
$('#account-content-scroller').on('click', '#account-edit-payments-link', function(evt) { evt.stopPropagation(); navToEditPayments(); return false; } );
$('#account-content-scroller').on('click', '#account-edit-audio-link', function(evt) { evt.stopPropagation(); navToEditAudio(); return false; } );
$('#account-content-scroller').on('avatar_changed', '#profile-avatar', function(evt, newAvatarUrl) { evt.stopPropagation(); updateAvatar(newAvatarUrl); return false; })
// License dialog:
$("#account-content-scroller").on('click', '#account-view-license-link', function(evt) {evt.stopPropagation(); app.layout.showDialog('jamtrack-license-dialog'); return false; } );
$("#account-content-scroller").on('click', '#account-payment-history-link', function(evt) {evt.stopPropagation(); app.layout.showDialog('jamtrack-payment-history-dialog'); return false; } );
}
function renderAccount() {
app.user().done(function() {
rest.getUserDetail()
.done(populateAccount)
.error(app.ajaxError)
.done(populateAccount)
.error(app.ajaxError)
})
}
function navToScheduledSessions() {
@ -115,6 +129,11 @@
window.location = '/client#/account/sessions'
}
function navToMyJamTracks() {
resetForm();
window.location = '/client#/account/jamtracks'
}
function navToEditIdentity() {
resetForm()
window.location = '/client#/account/identity'
@ -126,7 +145,7 @@
}
function navToEditSubscriptions() {
window.location = '/client#/account/profile'
}
function navToEditPayments() {

View File

@ -0,0 +1,103 @@
$ = jQuery
context = window
context.JK ||= {}
context.JK.AccountJamTracks = class AccountJamTracks
constructor: (@app) ->
@rest = context.JK.Rest()
@client = context.jamClient
@logger = context.JK.logger
@screen = null
@userId = context.JK.currentUserId;
initialize:() =>
screenBindings =
'beforeShow': @beforeShow
'afterShow': @afterShow
@app.bindScreen('account/jamtracks', screenBindings)
@screen = $('#account-jamtracks')
beforeShow:() =>
@logger.debug("beforeShow")
rest.getPurchasedJamTracks({})
.done(@populateJamTracks)
.fail(@app.ajaxError);
afterShow:() =>
@logger.debug("afterShow")
populateJamTracks:(data) =>
@logger.debug("populateJamTracks", data)
template = context._.template($('#template-account-jamtrack').html(), {jamtracks:data.jamtracks}, { variable: 'data' })
# template = context._.template($('#template-account-jamtrack').html(), {
# jamtracks: data.jamtracks
# current_user: @userId
# }, variable: 'data')
@logger.debug("TEMPLATE", template)
this.appendJamTracks template
@screen.find('.jamtrack-solo-session').on 'click', @soloSession
@screen.find('.jamtrack-group-session').on 'click', @groupSession
appendJamTracks:(template) =>
$('#account-my-jamtracks table tbody').replaceWith template
soloSession:(e) =>
#context.location="client#/createSession"
@logger.debug "BLEH", e
jamRow = $(e.target).parents("tr")
@logger.debug "BLEH2", e, jamRow.data()
@createSession(jamRow.data(), true)
#@logger.debug "BLEH", $(this), $(this).data()
groupSession:(e) =>
#context.location="client#/createSession"
jamRow = $(e.target).parents("tr")
@createSession(jamRow.data(), false)
createSession:(sessionData, solo) =>
tracks = context.JK.TrackHelpers.getUserTracks(context.jamClient)
if (context.JK.guardAgainstBrowser(@app))
@logger.debug("CRATING SESSION", sessionData.genre, solo)
data = {}
data.client_id = @app.clientId
#data.description = $('#description').val()
data.description = "Jam Track Session"
data.as_musician = true
data.legal_terms = true
data.intellectual_property = true
data.approval_required = false
data.musician_access = !solo
data.fan_access = false
data.fan_chat = false
data.genre = [sessionData.genre]
data.genres = [sessionData.genre]
# data.genres = context.JK.GenreSelectorHelper.getSelectedGenres('#create-session-genre')
# data.musician_access = if $('#musician-access option:selected').val() == 'true' then true else false
# data.approval_required = if $('input[name=\'musician-access-option\']:checked').val() == 'true' then true else false
# data.fan_access = if $('#fan-access option:selected').val() == 'true' then true else false
# data.fan_chat = if $('input[name=\'fan-chat-option\']:checked').val() == 'true' then true else false
# if $('#band-list option:selected').val() != ''
# data.band = $('#band-list option:selected').val()
data.audio_latency = context.jamClient.FTUEGetExpectedLatency().latency
data.tracks = tracks
rest.legacyCreateSession(data).done((response) =>
newSessionId = response.id
context.location = '/client#/session/' + newSessionId
# Re-loading the session settings will cause the form to reset with the right stuff in it.
# This is an extra xhr call, but it keeps things to a single codepath
loadSessionSettings()
context.JK.GA.trackSessionCount data.musician_access, data.fan_access, invitationCount
context.JK.GA.trackSessionMusicians context.JK.GA.SessionCreationTypes.create
).fail (jqXHR) =>
handled = false
if jqXHR.status = 422
response = JSON.parse(jqXHR.responseText)
if response['errors'] and response['errors']['tracks'] and response['errors']['tracks'][0] == 'Please select at least one track'
@app.notifyAlert 'No Inputs Configured', $('<span>You will need to reconfigure your audio device.</span>')
handled = true
if !handled
@app.notifyServerError jqXHR, 'Unable to Create Session'

View File

@ -168,13 +168,32 @@
});
$btnSubmit.click(function(evt) {
$document.triggerHandler(EVENTS.USER_UPDATED, response);
handleUpdateProfile();
if (validate()) {
handleUpdateProfile();
}
return false;
});
}
function validate() {
// website
if ($.trim($website.val()).length > 0) {
}
// SoundCloud
if ($.trim($soundCloudUsername.val()).length > 0) {
}
// ReverbNation
if ($.trim($reverbNationUsername.val())) {
}
return true;
}
function navigateTo(targetLocation) {
context.location = targetLocation;
}

View File

@ -35,6 +35,7 @@
//= require jquery.browser
//= require jquery.custom-protocol
//= require jquery.exists
//= require jquery.payment
//= require jstz
//= require class
//= require AAC_underscore

View File

@ -0,0 +1,385 @@
(function (context, $) {
"use strict";
context.JK = context.JK || {};
context.JK.CheckoutOrderScreen = function (app) {
var EVENTS = context.JK.EVENTS;
var logger = context.JK.logger;
var rest = context.JK.Rest();
var jamTrackUtils = context.JK.JamTrackUtils;
var $screen = null;
var $navigation = null;
var $templateOrderContent = null;
var $templatePurchasedJamTrack = null;
var $orderPanel = null;
var $thanksPanel = null;
var $jamTrackInBrowser = null;
var $purchasedJamTrack = null;
var $purchasedJamTrackHeader = null;
var $purchasedJamTracks = null;
var $orderContent = null;
var userDetail = null;
var step = null;
var downloadJamTracks = [];
var purchasedJamTracks = null;
var purchasedJamTrackIterator = 0;
var $backBtn = null;
var $orderPrompt = null;
var $emptyCartPrompt = null;
var $noAccountInfoPrompt = null;
function beforeShow() {
beforeShowOrder();
}
function afterShow(data) {
}
function beforeHide() {
if(downloadJamTracks) {
context._.each(downloadJamTracks, function(downloadJamTrack) {
downloadJamTrack.destroy();
downloadJamTrack.root.remove();
})
downloadJamTracks = [];
}
purchasedJamTracks = null;
purchasedJamTrackIterator = 0;
}
function beforeShowOrder() {
$orderPrompt.addClass('hidden')
$emptyCartPrompt.addClass('hidden')
$noAccountInfoPrompt.addClass('hidden')
$orderPanel.removeClass("hidden")
$thanksPanel.addClass("hidden")
$screen.find(".place-order").addClass('disabled').off('click', placeOrder)
$("#order_error").text('').addClass("hidden")
step = 3;
renderNavigation();
populateOrderPage();
}
function populateOrderPage() {
clearOrderPage();
rest.getShoppingCarts()
.done(function(carts) {
rest.getBillingInfo()
.done(function(billingInfo) {
renderOrderPage(carts, billingInfo)
})
.fail(function(jqXHR) {
if(jqXHR.status == 404) {
// no account for this user
$noAccountInfoPrompt.removeClass('hidden')
app.notify({ title: "No account information",
text: "Please restart the checkout process." },
null,
true);
}
})
})
.fail(app.ajaxError);
}
function renderOrderPage(carts, recurlyAccountInfo) {
logger.debug("rendering order page")
var data = {}
var sub_total = 0.0
var taxes = 0.0
$.each(carts, function(index, cart) {
sub_total += parseFloat(cart.product_info.total_price)
});
if(carts.length == 0) {
data.grand_total = '-.--'
data.sub_total = '-.--'
data.taxes = '-.--'
data.shipping_handling = '-.--'
}
else {
data.grand_total = 'Calculating...'
data.sub_total = '$' + sub_total.toFixed(2)
data.taxes = 'Calculating...'
data.shipping_handling = '$0.00'
}
data.carts = carts
data.billing_info = recurlyAccountInfo.billing_info
data.shipping_info = recurlyAccountInfo.address
data.shipping_as_billing = true; //jamTrackUtils.compareAddress(data.billing_info, data.shipping_info);
var orderContentHtml = $(
context._.template(
$templateOrderContent.html(),
data,
{variable: 'data'}
)
)
$orderContent.append(orderContentHtml)
$orderPanel.find(".change-payment-info").on('click', moveToPaymentInfo)
var $placeOrder = $screen.find(".place-order")
if(carts.length == 0) {
$orderPrompt.addClass('hidden')
$emptyCartPrompt.removeClass('hidden')
$noAccountInfoPrompt.addClass('hidden')
$placeOrder.addClass('disabled')
}
else {
logger.debug("cart has " + carts.length + " items in it")
$orderPrompt.removeClass('hidden')
$emptyCartPrompt.addClass('hidden')
$noAccountInfoPrompt.addClass('hidden')
$placeOrder.removeClass('disabled').on('click', placeOrder)
var planPricing = {}
context._.each(carts, function(cart) {
var priceElement = $screen.find('.order-right-page .plan[data-plan-code="' + cart.product_info.plan_code +'"]')
if(priceElement.length == 0) {
logger.error("unable to find price element for " + cart.product_info.plan_code, cart);
app.notify({title: "Error Encountered", text: "Unable to find plan info for " + cart.product_info.plan_code})
return false;
}
logger.debug("creating recurly pricing element for plan: " + cart.product_info.plan_code)
var pricing = context.recurly.Pricing();
pricing.plan_code = cart.product_info.plan_code;
pricing.resolved = false;
pricing.effective_quantity = cart.product_info.quantity - cart.product_info.marked_for_redeem
planPricing[pricing.plan_code] = pricing;
// this is called when the plan is resolved against Recurly. It will have tax info, which is the only way we can get it.
pricing.on('change', function(price) {
var resolvedPrice = planPricing[this.plan_code];
if(!resolvedPrice) {
logger.error("unable to find price info in storage")
app.notify({title: "Error Encountered", text: "Unable to find plan info in storage"})
return;
}
else {
logger.debug("pricing resolved for plan: " + this.plan_code)
}
resolvedPrice.resolved = true;
var allResolved = true;
var totalTax = 0;
var totalPrice = 0;
// let's see if all plans have been resolved via API; and add up total price and taxes for display
$.each(planPricing, function(plan_code, priceObject) {
logger.debug("resolved recurly priceObject", priceObject)
if(!priceObject.resolved) {
allResolved = false;
return false;
}
else {
var unitTax = Number(priceObject.price.now.tax) * priceObject.effective_quantity;
totalTax += unitTax;
var totalUnitPrice = Number(priceObject.price.now.total) * priceObject.effective_quantity;
totalPrice += totalUnitPrice;
}
})
if(allResolved) {
$screen.find('.order-right-page .order-items-value.taxes').text('$' + totalTax.toFixed(2))
$screen.find('.order-right-page .order-items-value.grand-total').text('$' + totalPrice.toFixed(2))
}
else
{
logger.debug("still waiting on more plans to resolve")
}
})
pricing.attach(priceElement.eq(0))
})
}
}
function moveToPaymentInfo() {
context.location = '/client#/checkoutPayment';
return false;
}
function placeOrder(e) {
e.preventDefault();
$screen.find(".place-order").off('click').addClass('disabled')
$("#order_error").text('').addClass("hidden")
rest.placeOrder()
.done(moveToThanks)
.fail(orderErrorHandling);
}
function orderErrorHandling(xhr, ajaxOptions, thrownError) {
if (xhr && xhr.responseJSON) {
var message = "Error submitting payment: "
$.each(xhr.responseJSON.errors, function (key, error) {
message += key + ": " + error
})
$("#order_error").text(message).removeClass("hidden")
}
else {
$("#order_error").text(xhr.responseText).removeClass("hidden")
}
$screen.find(".place-order").on('click', placeOrder).removeClass('disabled')
}
function moveToThanks(purchaseResponse) {
$("#order_error").addClass("hidden")
$orderPanel.addClass("hidden")
$thanksPanel.removeClass("hidden")
jamTrackUtils.checkShoppingCart()
handleJamTracksPurchased(purchaseResponse.jam_tracks)
}
function handleJamTracksPurchased(jamTracks) {
// were any JamTracks purchased?
var jamTracksPurchased = jamTracks && jamTracks.length > 0;
if(jamTracksPurchased) {
if(gon.isNativeClient) {
startDownloadJamTracks(jamTracks)
}
else {
$jamTrackInBrowser.removeClass('hidden');
}
}
}
function startDownloadJamTracks(jamTracks) {
// there can be multiple purchased JamTracks, so we cycle through them
purchasedJamTracks = jamTracks;
// populate list of jamtracks purchased, that we will iterate through graphically
context._.each(jamTracks, function(jamTrack) {
var downloadJamTrack = new context.JK.DownloadJamTrack(app, jamTrack, 'small');
var $purchasedJamTrack = $(context._.template(
$templatePurchasedJamTrack.html(),
jamTrack,
{variable: 'data'}
));
$purchasedJamTracks.append($purchasedJamTrack)
// show it on the page
$purchasedJamTrack.append(downloadJamTrack.root)
downloadJamTracks.push(downloadJamTrack)
})
iteratePurchasedJamTracks();
}
function iteratePurchasedJamTracks() {
if(purchasedJamTrackIterator < purchasedJamTracks.length ) {
var downloadJamTrack = downloadJamTracks[purchasedJamTrackIterator++];
// make sure the 'purchasing JamTrack' section can be seen
$purchasedJamTrack.removeClass('hidden');
// the widget indicates when it gets to any transition; we can hide it once it reaches completion
$(downloadJamTrack).on(EVENTS.JAMTRACK_DOWNLOADER_STATE_CHANGED, function(e, data) {
if(data.state == downloadJamTrack.states.synchronized) {
logger.debug("jamtrack " + downloadJamTrack.jamTrack.name + " synchronized;")
//downloadJamTrack.root.remove();
downloadJamTrack.destroy();
// go to the next JamTrack
iteratePurchasedJamTracks()
}
})
logger.debug("jamtrack " + downloadJamTrack.jamTrack.name + " downloader initializing")
// kick off the download JamTrack process
downloadJamTrack.init()
// XXX style-test code
// downloadJamTrack.transitionError("package-error", "The server failed to create your package.")
}
else {
logger.debug("done iterating over purchased JamTracks")
$purchasedJamTrackHeader.text('All purchased JamTracks have been downloaded successfully! You can now play them in a session.')
}
}
function clearOrderPage() {
$orderContent.empty();
}
function renderNavigation() {
$navigation.html("");
var navigationHtml = $(
context._.template(
$('#template-checkout-navigation').html(),
{current: step},
{variable: 'data'}
)
);
$navigation.append(navigationHtml);
}
function events() {
$backBtn.on('click', function(e) {
e.preventDefault();
context.location = '/client#/checkoutPayment'
})
}
function initialize() {
var screenBindings = {
'beforeShow': beforeShow,
'afterShow': afterShow,
'beforeHide': beforeHide
};
app.bindScreen('checkoutOrder', screenBindings);
$screen = $("#checkoutOrderScreen");
$navigation = $screen.find(".checkout-navigation-bar");
$templateOrderContent = $("#template-order-content");
$templatePurchasedJamTrack = $('#template-purchased-jam-track');
$orderPanel = $screen.find(".order-panel");
$thanksPanel = $screen.find(".thanks-panel");
$jamTrackInBrowser = $screen.find(".thanks-detail.jam-tracks-in-browser");
$purchasedJamTrack = $thanksPanel.find(".thanks-detail.purchased-jam-track");
$purchasedJamTrackHeader = $purchasedJamTrack.find(".purchased-jam-track-header");
$purchasedJamTracks = $purchasedJamTrack.find(".purchased-list")
$backBtn = $screen.find('.back');
$orderPrompt = $screen.find('.order-prompt');
$emptyCartPrompt = $screen.find('.empty-cart-prompt');
$noAccountInfoPrompt = $screen.find('.no-account-info-prompt');
$orderContent = $orderPanel.find(".order-content");
if ($screen.length == 0) throw "$screen must be specified";
if ($navigation.length == 0) throw "$navigation must be specified";
events();
}
this.initialize = initialize;
return this;
}
})
(window, jQuery);

View File

@ -0,0 +1,667 @@
(function(context,$) {
"use strict";
context.JK = context.JK || {};
context.JK.CheckoutPaymentScreen = function(app) {
var EVENTS = context.JK.EVENTS;
var logger = context.JK.logger;
var jamTrackUtils = context.JK.JamTrackUtils;
var $screen = null;
var $navigation = null;
var $billingInfo = null;
var $shippingInfo = null;
var $paymentMethod = null;
var $shippingAddress = null;
var $shippingAsBilling = null;
var $paymentInfoPanel = null;
var $accountSignup = null;
var userDetail = null;
var step = null;
var billing_info = null;
var shipping_info = null;
var shipping_as_billing = null;
var $reuseExistingCard = null;
var $reuseExistingCardChk = null;
var $existingCardEndsWith = null;
var $newCardInfo = null;
var selectCountry = null;
var selectCountryLoaded = false;
var $freeJamTrackPrompt = null;
var $noFreeJamTrackPrompt = null;
function afterShow() {
beforeShowPaymentInfo();
}
function beforeShowPaymentInfo() {
step = 2;
renderNavigation();
renderAccountInfo();
}
function renderAccountInfo() {
$reuseExistingCard.addClass('hidden');
$newCardInfo.removeClass('hidden');
$freeJamTrackPrompt.addClass('hidden');
$noFreeJamTrackPrompt.addClass('hidden');
$("#payment_error").addClass('hidden').text('')
var selectCountryReady = selectCountry.ready();
if(!selectCountryReady) {
// one time init of country dropdown
selectCountryReady = selectCountry.load('US', null, null);
}
selectCountryReady.done(function() {
var user = rest.getUserDetail()
if(user) {
user.done(populateAccountInfo).error(app.ajaxError);
}
else {
$reuseExistingCardChk.iCheck('uncheck').attr('checked', false)
if(gon.global.one_free_jamtrack_per_user) {
$freeJamTrackPrompt.removeClass('hidden')
}
else {
$noFreeJamTrackPrompt.removeClass('hidden')
}
}
})
}
function populateAccountInfo(user) {
userDetail = user;
$reuseExistingCardChk.iCheck(userDetail.reuse_card && userDetail.has_recurly_account ? 'check' : 'uncheck').attr('checked', userDetail.reuse_card)
// show appropriate prompt text based on whether user has a free jamtrack
if(user.free_jamtrack) {
$freeJamTrackPrompt.removeClass('hidden')
}
else {
$noFreeJamTrackPrompt.removeClass('hidden')
}
if (userDetail.has_recurly_account) {
rest.getBillingInfo()
.done(function(response) {
if(userDetail.reuse_card) {
$reuseExistingCard.removeClass('hidden');
toggleReuseExistingCard.call($reuseExistingCardChk)
$existingCardEndsWith.text(response.billing_info.last_four);
}
var isSameAsShipping = true // jamTrackUtils.compareAddress(response.billing_info, response.address);
$shippingAsBilling.iCheck(isSameAsShipping ? 'check' : 'uncheck').attr('checked', isSameAsShipping)
$billingInfo.find("#billing-first-name").val(response.billing_info.first_name);
$billingInfo.find("#billing-last-name").val(response.billing_info.last_name);
$billingInfo.find("#billing-address1").val(response.billing_info.address1);
$billingInfo.find("#billing-address2").val(response.billing_info.address2);
$billingInfo.find("#billing-city").val(response.billing_info.city);
$billingInfo.find("#billing-state").val(response.billing_info.state);
$billingInfo.find("#billing-zip").val(response.billing_info.zip);
$billingInfo.find("#billing-country").val(response.billing_info.country);
//$shippingAddress.find("#shipping-first-name").val(response.billing_info.first_name);
//$shippingAddress.find("#shipping-last-name").val(response.billing_info.last_name);
//$shippingAddress.find("#shipping-address1").val(response.address.address1);
//$shippingAddress.find("#shipping-address2").val(response.address.address2);
//$shippingAddress.find("#shipping-city").val(response.address.city);
//$shippingAddress.find("#shipping-state").val(response.address.state);
//$shippingAddress.find("#shipping-zip").val(response.address.zip);
//$shippingAddress.find("#shipping-country").val(response.address.country);
})
.error(app.ajaxError);
}
else {
$billingInfo.find("#billing-first-name").val(userDetail.first_name);
$billingInfo.find("#billing-last-name").val(userDetail.last_name);
$billingInfo.find("#billing-city").val(userDetail.city);
$billingInfo.find("#billing-state").val(userDetail.state);
$billingInfo.find("#billing-country").val(userDetail.country);
$shippingAddress.find("#shipping-first-name").val(userDetail.first_name);
$shippingAddress.find("#shipping-last-name").val(userDetail.last_name);
$shippingAddress.find("#shipping-city").val(userDetail.city);
$shippingAddress.find("#shipping-state").val(userDetail.state);
$shippingAddress.find("#shipping-country").val(userDetail.country);
}
}
function beforeShow(data) {
// XXX : style-test code
// moveToThanks({jam_tracks: [{id: 14, jam_track_right_id: 11, name: 'Back in Black'}, {id: 15, jam_track_right_id: 11, name: 'In Bloom'}, {id: 16, jam_track_right_id: 11, name: 'Love Bird Supreme'}]});
}
function beforeHide() {
}
function beforeHide() {
}
// TODO: Refactor: this function is long and fraught with many return points.
function next(e) {
$paymentInfoPanel.find('.error-text').remove();
$paymentInfoPanel.find('.error').removeClass('error');
e.preventDefault();
$("#payment_error").addClass("hidden").text('')
var reuse_card_this_time = $reuseExistingCardChk.is(':checked');
var reuse_card_next_time = $paymentMethod.find('#save-card').is(':checked');
// validation
var billing_first_name = $billingInfo.find("#billing-first-name").val();
var billing_last_name = $billingInfo.find("#billing-last-name").val();
var billing_address1 = $billingInfo.find("#billing-address1").val();
var billing_address2 = $billingInfo.find("#billing-address2").val();
var billing_city = $billingInfo.find("#billing-city").val();
var billing_state = $billingInfo.find("#billing-state").val();
var billing_zip = $billingInfo.find("#billing-zip").val();
var billing_country = $billingInfo.find("#billing-country").val();
var billingInfoValid = true;
if (!billing_first_name) {
$billingInfo.find('#divBillingFirstName .error-text').remove();
$billingInfo.find('#divBillingFirstName').addClass("error").addClass("transparent");
$billingInfo.find('#billing-first-name').after("<ul class='error-text'><li>First Name is required</li></ul>");
logger.info("no billing first name");
billingInfoValid = false;
}
else {
$billingInfo.find('#divBillingFirstName').removeClass("error");
}
if (!billing_last_name) {
$billingInfo.find('#divBillingLastName .error-text').remove();
$billingInfo.find('#divBillingLastName').addClass("error").addClass("transparent");
$billingInfo.find('#billing-last-name').after("<ul class='error-text'><li>Last Name is required</li></ul>");
logger.info("no billing last name");
billingInfoValid = false;
}
else {
$billingInfo.find('#divBillingLastName').removeClass("error");
}
if (!billing_address1) {
$billingInfo.find('#divBillingAddress1 .error-text').remove();
$billingInfo.find('#divBillingAddress1').addClass("error").addClass("transparent");
$billingInfo.find('#billing-address1').after("<ul class='error-text'><li>Address is required</li></ul>");
logger.info("no billing address line 1");
billingInfoValid = false;
}
else {
$billingInfo.find('#divBillingAddress1').removeClass("error");
}
if (!billing_zip) {
$billingInfo.find('#divBillingZip .error-text').remove();
$billingInfo.find('#divBillingZip').addClass("error").addClass("transparent");
$billingInfo.find('#billing-zip').after("<ul class='error-text'><li>Zip Code is required</li></ul>");
logger.info("no billing zip");
billingInfoValid = false;
}
else {
$billingInfo.find('#divBillingZip').removeClass("error");
}
if (!billing_state) {
$billingInfo.find('#divBillingState .error-text').remove();
$billingInfo.find('#divBillingState').addClass("error").addClass("transparent");
$billingInfo.find('#billing-state').after("<ul class='error-text'><li>State is required</li></ul>");
logger.info("no billing state");
billingInfoValid = false;
}
else {
$billingInfo.find('#divBillingState').removeClass("error");
}
if (!billing_city) {
$billingInfo.find('#divBillingCity .error-text').remove();
$billingInfo.find('#divBillingCity').addClass("error").addClass("transparent");
$billingInfo.find('#billing-city').after("<ul class='error-text'><li>City is required</li></ul>");
logger.info("no billing city");
billingInfoValid = false;
}
else {
$billingInfo.find('#divBillingCity').removeClass("error");
}
if (!billing_country) {
$billingInfo.find('#divBillingCountry .error-text').remove();
$billingInfo.find('#divBillingCountry').addClass("error").addClass("transparent");
$billingInfo.find('#billing-country').after("<ul class='error-text'><li>Country is required</li></ul>");
logger.info("no billing country");
billingInfoValid = false;
}
else {
$billingInfo.find('#divBillingCountry').removeClass("error");
}
/**
shipping_as_billing = $shippingAsBilling.is(":checked");
var shipping_first_name, shipping_last_name, shipping_address1, shipping_address2;
var shipping_city, shipping_state, shipping_zip, shipping_country;
if (!shipping_as_billing) {
shipping_first_name = $shippingAddress.find("#shipping-first-name").val();
shipping_last_name = $shippingAddress.find("#shipping-last-name").val();
shipping_address1 = $shippingAddress.find("#shipping-address1").val();
shipping_address2 = $shippingAddress.find("#shipping-address2").val();
shipping_city = $shippingAddress.find("#shipping-city").val();
shipping_state = $shippingAddress.find("#shipping-state").val();
shipping_zip = $shippingAddress.find("#shipping-zip").val();
shipping_country = $shippingAddress.find("#shipping-country").val();
if (!shipping_first_name) {
$shippingAddress.find('#divShippingFirstName .error-text').remove();
$shippingAddress.find('#divShippingFirstName').addClass("error").addClass("transparent");
$shippingAddress.find('#shipping-first-name').after("<ul class='error-text'><li>First Name is required</li></ul>");
logger.info("no address first name");
return false;
}
else {
$shippingInfo.find('#divShippingFirstName').removeClass("error");
}
if (!shipping_last_name) {
$shippingAddress.find('#divShippingLastName .error-text').remove();
$shippingAddress.find('#divShippingLastName').addClass("error").addClass("transparent");
$shippingAddress.find('#shipping-last-name').after("<ul class='error-text'><li>Last Name is required</li></ul>");
logger.info("no last name");
return false;
}
else {
$shippingInfo.find('#divShippingLastName').removeClass("error");
}
if (!shipping_address1) {
$shippingAddress.find('#divShippingAddress1 .error-text').remove();
$shippingAddress.find('#divShippingAddress1').addClass("error").addClass("transparent");
$shippingAddress.find('#shipping-address1').after("<ul class='error-text'><li>Address is required</li></ul>");
logger.info("no shipping address 1");
return false;
}
else {
$shippingInfo.find('#divShippingAddress1').removeClass("error");
}
if (!shipping_zip) {
$shippingAddress.find('#divShippingZip .error-text').remove();
$shippingAddress.find('#divShippingZip').addClass("error").addClass("transparent");
$shippingAddress.find('#shipping-zip').after("<ul class='error-text'><li>Zip code is required</li></ul>");
logger.info("no shipping address 2");
return false;
}
else {
$shippingInfo.find('#divShippingZip').removeClass("error");
}
if (!shipping_state) {
$shippingAddress.find('#divShippingState .error-text').remove();
$shippingAddress.find('#divShippingState').addClass("error").addClass("transparent");
$shippingAddress.find('#shipping-zip').after("<ul class='error-text'><li>State is required</li></ul>");
logger.info("no shipping state");
return false;
}
else {
$shippingInfo.find('#divShippingState').removeClass("error");
}
if (!shipping_city) {
$shippingAddress.find('#divShippingCity .error-text').remove();
$shippingAddress.find('#divShippingCity').addClass("error").addClass("transparent");
$shippingAddress.find('#shipping-city').after("<ul class='error-text'><li>City is required</li></ul>");
logger.info("no shipping city");
return false;
}
else {
$shippingInfo.find('#divShippingCity').removeClass("error");
}
if (!shipping_country) {
$shippingAddress.find('#divShippingCountry .error-text').remove();
$shippingAddress.find('#divShippingCountry').addClass("error").addClass("transparent");
$shippingAddress.find('#shipping-country').after("<ul class='error-text'><li>Country is required</li></ul>");
logger.info("no shipping country");
return false;
}
else {
$shippingAddress.find('#divShippingCountry').removeClass("error");
}
}
*/
var card_name = $paymentMethod.find("#card-name").val();
var card_number = $paymentMethod.find("#card-number").val();
var card_year = $paymentMethod.find("#card_expire-date_1i").val();
var card_month = $paymentMethod.find("#card_expire-date_2i").val();
var card_verify = $paymentMethod.find("#card-verify").val();
/**
if (!card_name) {
$paymentMethod.find('#divCardName .error-text').remove();
$paymentMethod.find('#divCardName').addClass("error").addClass("transparent");
$paymentMethod.find('#card-name').after("<ul class='error-text'><li>Card Name is required</li></ul>");
return false;
} else {
$paymentMethod.find('#divCardName').removeClass("error");
}*/
// don't valid card form fields when reuse card selected
if(!reuse_card_this_time) {
if (!card_number) {
$paymentMethod.find('#divCardNumber .error-text').remove();
$paymentMethod.find('#divCardNumber').addClass("error").addClass("transparent");
$paymentMethod.find('#card-number').after("<ul class='error-text'><li>Card Number is required</li></ul>");
logger.info("no card number");
billingInfoValid = false;
} else if (!$.payment.validateCardNumber(card_number)) {
$paymentMethod.find('#divCardNumber .error-text').remove();
$paymentMethod.find('#divCardNumber').addClass("error").addClass("transparent");
$paymentMethod.find('#card-number').after("<ul class='error-text'><li>Card Number is not valid</li></ul>");
logger.info("invalid card number");
billingInfoValid = false;
} else {
$paymentMethod.find('#divCardNumber').removeClass("error");
}
if (!$.payment.validateCardExpiry(card_month, card_year)) {
$paymentMethod.find('#divCardExpiry .error-text').remove();
$paymentMethod.find('#divCardExpiry').addClass("error").addClass("transparent");
$paymentMethod.find('#card-expiry').after("<ul class='error-text'><li>Card Number is not valid</li></ul>");
logger.info("invalid card expiry");
billingInfoValid = false;
} else {
$paymentMethod.find('#divCardExpiry').removeClass("error");
}
if (!card_verify) {
$paymentMethod.find('#divCardVerify .error-text').remove();
$paymentMethod.find('#divCardVerify').addClass("error").addClass("transparent");
$paymentMethod.find('#card-verify').after("<ul class='error-text'><li>Card Verification Value is required</li></ul>");
logger.info("no card verify");
billingInfoValid = false;
} else if (!$.payment.validateCardCVC(card_verify)) {
$paymentMethod.find('#divCardVerify .error-text').remove();
$paymentMethod.find('#divCardVerify').addClass("error").addClass("transparent");
$paymentMethod.find('#card-verify').after("<ul class='error-text'><li>Card Verification Value is not valid.</li></ul>");
logger.info("bad card CVC");
billingInfoValid = false;
} else {
$paymentMethod.find('#divCardVerify').removeClass("error");
}
}
if(!billingInfoValid) {
logger.debug("billing info is invalid. returning");
return false;
}
billing_info = {};
shipping_info = {};
billing_info.first_name = billing_first_name;
billing_info.last_name = billing_last_name;
billing_info.address1 = billing_address1;
billing_info.address2 = billing_address2;
billing_info.city = billing_city;
billing_info.state = billing_state;
billing_info.country = billing_country;
billing_info.zip = billing_zip;
billing_info.number = card_number;
billing_info.month = card_month;
billing_info.year = card_year;
billing_info.verification_value = card_verify;
/**
if (shipping_as_billing) {
shipping_info = $.extend({},billing_info);
delete shipping_info.number;
delete shipping_info.month;
delete shipping_info.year;
delete shipping_info.verification_value;
} else {
shipping_info.first_name = shipping_first_name;
shipping_info.last_name = shipping_last_name;
shipping_info.address1 = shipping_address1;
shipping_info.address2 = shipping_address2;
shipping_info.city = shipping_city;
shipping_info.state = shipping_state;
shipping_info.country = shipping_country;
shipping_info.zip = shipping_zip;
}*/
var email = null;
var password = null;
var terms = false;
var isLoggedIn = context.JK.currentUserId;
if(!isLoggedIn) {
email = $accountSignup.find('input[name="email"]').val()
password = $accountSignup.find('input[name="password"]').val()
terms = $accountSignup.find('input[name="terms-of-service"]').is(':checked');
}
$screen.find("#payment-info-next").addClass("disabled");
$screen.find("#payment-info-next").off("click");
rest.createRecurlyAccount({billing_info: billing_info, terms_of_service: terms, email: email, password: password, reuse_card_this_time: reuse_card_this_time, reuse_card_next_time: reuse_card_next_time})
.done(function() {
$screen.find("#payment-info-next").on("click", next);
if(isLoggedIn) {
context.location = '/client#/checkoutOrder'
}
else {
// this means the account was created; we need to reload the page for this to take effect
context.JK.currentUserId = 'something' // this is to trick layout.js from getting involved and redirecting to home screen
context.location = '/client#/checkoutOrder'
context.location.reload()
}
})
.fail(errorHandling)
.always(function(){
$screen.find("#payment-info-next").removeClass("disabled");
})
}
function errorHandling(xhr, ajaxOptions, thrownError) {
logger.debug("error handling", xhr.responseJSON)
if(xhr.responseJSON && xhr.responseJSON.errors) {
$.each(xhr.responseJSON.errors, function(key, error) {
if (key == 'number') {
$paymentMethod.find('#divCardNumber .error-text').remove();
$paymentMethod.find('#divCardNumber').addClass("error").addClass("transparent");
$paymentMethod.find('#card-number').after("<ul class='error-text'><li>" + error + "</li></ul>");
}
else if (key == 'verification_value') {
$paymentMethod.find('#divCardVerify .error-text').remove();
$paymentMethod.find('#divCardVerify').addClass("error").addClass("transparent");
$paymentMethod.find('#card-verify').after("<ul class='error-text'><li>" + error + "</li></ul>");
}
else if(key == 'email') {
var $email = $accountSignup.find('input[name="email"]')
var $field = $email.closest('.field')
$field.find('.error-text').remove()
$field.addClass("error").addClass("transparent");
$email.after("<ul class='error-text'><li>" + error + "</li></ul>");
}
else if(key == 'password') {
var $password = $accountSignup.find('input[name="password"]')
var $field = $password.closest('.field')
$field.find('.error-text').remove()
$field.addClass("error").addClass("transparent");
$password.after("<ul class='error-text'><li>" + error + "</li></ul>");
}
else if(key == 'terms_of_service') {
var $terms = $accountSignup.find('input[name="terms-of-service"]')
var $field = $terms.closest('.field')
$field.find('.error-text').remove()
$field.addClass("error").addClass("transparent");
$accountSignup.find('.terms-of-service-label-holder').append("<ul class='error-text'><li>" + error + "</li></ul>");
}
else if(key == 'message') {
$("#payment_error").text(error).removeClass('hidden')
}
});
}
else {
$("#payment_error").text(xhr.responseText).removeClass('hidden')
}
$screen.find("#payment-info-next").on('click', next);
}
function beforeShowOrder() {
step = 3;
renderNavigation();
populateOrderPage();
}
function populateOrderPage() {
rest.getShoppingCarts()
.done(renderOrderPage)
.fail(app.ajaxError);
}
function toggleShippingAsBilling(e) {
e.preventDefault();
var shipping_as_billing = $(e.target).is(':checked');
if (!shipping_as_billing) {
$shippingAddress.removeClass("hidden");
}
else {
$shippingAddress.addClass("hidden");
}
}
function toggleReuseExistingCard(e) {
if(e) {
e.preventDefault();
}
logger.debug("toggle reuse existing card")
var reuse_existing = $(this).is(':checked');
$('#billing-first-name').prop('disabled', reuse_existing);
$('#billing-last-name').prop('disabled', reuse_existing);
$('#billing-address1').prop('disabled', reuse_existing);
$('#billing-address2').prop('disabled', reuse_existing);
$('#billing-city').prop('disabled', reuse_existing);
$('#billing-state').prop('disabled', reuse_existing);
$('#billing-zip').prop('disabled', reuse_existing);
$('#billing-country').prop('disabled', reuse_existing);
$('#card-name').prop('disabled', reuse_existing);
$('#card-number').prop('disabled', reuse_existing);
$('#card_expire-date_1i').prop('disabled', reuse_existing);
$('#card_expire-date_2i').prop('disabled', reuse_existing);
$('#card-verify').prop('disabled', reuse_existing);
}
function events() {
$screen.find("#payment-info-next").on('click', next);
$shippingAsBilling.on('ifChanged', toggleShippingAsBilling);
$reuseExistingCardChk.on('ifChanged', toggleReuseExistingCard);
}
function reset() {
}
function renderNavigation() {
$navigation.html("");
var navigationHtml = $(
context._.template(
$('#template-checkout-navigation').html(),
{current: step},
{variable: 'data'}
)
);
$navigation.append(navigationHtml);
}
function initializeControls() {
$("form.payment-info").addClass(context.JK.currentUserId ? 'signed-in' : 'not-signed-in').iCheck({
checkboxClass: 'icheckbox_minimal',
radioClass: 'iradio_minimal',
inheritClass: true
});
// Use jquery.payment to limit characters and length:
$paymentMethod.find("#card-number").payment('formatCardNumber');
$paymentMethod.find("#card-verify").payment('formatCardCVC');
selectCountry = new context.JK.SelectLocation($('#billing-country'), null, null, app, false)
}
function initialize() {
var screenBindings = {
'beforeShow': beforeShow,
'afterShow': afterShow,
'beforeHide' : beforeHide
};
app.bindScreen('checkoutPayment', screenBindings);
$screen = $("#checkoutPaymentScreen");
$paymentInfoPanel = $screen.find("#checkout-payment-info");
$navigation = $screen.find(".checkout-navigation-bar");
$billingInfo = $paymentInfoPanel.find(".billing-address");
$shippingInfo = $paymentInfoPanel.find(".shipping-address");
$paymentMethod = $paymentInfoPanel.find(".payment-method");
$accountSignup = $paymentInfoPanel.find('.jamkazam-account-signup')
$shippingAddress = $paymentInfoPanel.find(".shipping-address-detail");
$shippingAsBilling = $paymentInfoPanel.find("#shipping-as-billing");
$reuseExistingCard = $paymentInfoPanel.find('.reuse-existing-card')
$reuseExistingCardChk = $paymentInfoPanel.find('#reuse-existing-card')
$existingCardEndsWith = $paymentInfoPanel.find('.existing-card-ends-with')
$newCardInfo = $paymentInfoPanel.find('.new-card-info')
$freeJamTrackPrompt = $screen.find('.payment-prompt.free-jamtrack')
$noFreeJamTrackPrompt = $screen.find('.payment-prompt.no-free-jamtrack')
if($screen.length == 0) throw "$screen must be specified";
if($navigation.length == 0) throw "$navigation must be specified";
initializeControls();
events();
}
this.initialize = initialize;
return this;
}
})(window,jQuery);

View File

@ -14,54 +14,77 @@
var $password = null;
var $signinBtn = null;
var $signupBtn = null;
var $inputElements = null;
var $contentHolder = null;
var $btnNext = null;
function beforeShow(data) {
renderNavigation();
renderLoggedInState();
}
function afterShow(data) {
}
function events() {
$signinBtn.on('click', login);
$signupBtn.on('click', signup);
function renderLoggedInState(){
if(isLoggedIn()) {
$contentHolder.removeClass('not-signed-in').addClass('signed-in')
}
else {
$contentHolder.removeClass('signed-in').addClass('not-signed-in')
}
}
function signup(e) {
app.layout.showDialog('signup-dialog');
return false;
function isLoggedIn() {
return !!context.JK.currentUserId;
}
function events() {
$signinForm.on('submit', login);
$signinBtn.on('click', login);
$btnNext.on('click', moveNext);
}
function reset() {
$signinForm.removeClass('login-error');
$email.val('');
$password.val('');
$inputElements.removeClass('login-error');
}
function moveNext() {
window.location = '/client#/checkoutPayment';
return false;
}
function login() {
if($signinBtn.is('.disabled')) {
return false;
}
var email = $email.val();
var password = $password.val();
reset();
$signinBtn.text('TRYING...');
$signinBtn.text('TRYING...').addClass('disabled')
rest.login({email: email, password: password, remember_me: false})
rest.login({email: email, password: password, remember_me: true})
.done(function() {
window.location = '/client#/order'
window.location = '/client#/checkoutPayment'
window.location.reload();
})
.fail(function(jqXHR) {
if(jqXHR.status == 422) {
$signinForm.addClass('login-error')
$inputElements.addClass('login-error')
}
else {
app.notifyServerError(jqXHR, "Unable to log in")
}
})
.always(function() {
$signinBtn.text('SIGN IN')
$signinBtn.text('SIGN IN').removeClass('disabled')
})
return false;
}
function renderNavigation() {
@ -83,15 +106,17 @@
'beforeShow': beforeShow,
'afterShow': afterShow
};
app.bindScreen('signin', screenBindings);
app.bindScreen('checkoutSignin', screenBindings);
$screen = $("#signInScreen");
$screen = $("#checkoutSignInScreen");
$navigation = $screen.find(".checkout-navigation-bar");
$signinForm = $screen.find(".signin-form");
$signinBtn = $signinForm.find('.signin-submit');
$email = $signinForm.find('input[name="session[email]"]');
$password = $signinForm.find('input[name="session[password]"]');
$signupBtn = $signinForm.find('.show-signup-dialog');
$email = $signinForm.find('input[name="email"]');
$password = $signinForm.find('input[name="password"]');
$inputElements = $signinForm.find('.input-elements');
$contentHolder = $screen.find('.content-holder');
$btnNext = $screen.find('.btnNext');
if($screen.length == 0) throw "$screen must be specified";
if($navigation.length == 0) throw "$navigation must be specified";

View File

@ -0,0 +1,62 @@
$ = jQuery
context = window
context.JK ||= {};
# events emitted:
# EVENTS.CHECKOUT_SIGNED_IN
# EVENTS.CHECKOUT_SKIP_SIGN_IN
context.JK.CheckoutSignin = class CheckoutSignin
constructor: (app, root) ->
@EVENTS = context.JK.EVENTS
@rest = context.JK.Rest()
@logger = context.JK.logger
@app = app
@root = root
@emailInput = @root.find('input[name="email"]')
@passwordInput = @root.find('input[name="password"]')
@signinBtn = @root.find('.signin-submit')
@skipSigninBtn = @root.find('.btnNext')
@signinForm = @root.find('form.signin-form')
# TODO: add Facebook login support
throw "no root element" if not @root.exists()
throw "no email element" if not @emailInput.exists()
throw "no password element" if not @passwordInput.exists()
throw "no signin btn" if not @signinBtn.exists()
throw "no skip signin btn" if not @skipSigninBtn.exists()
@signinForm.submit(@onSigninSubmit)
@skipSigninBtn.click(@onSkipSignin)
removeErrors: () =>
@signinForm.removeClass('login-error');
onSigninSubmit: () =>
@logger.debug("attempting to signin")
@removeErrors()
@signinBtn.addClass('disabled')
email = @emailInput.val()
password = @passwordInput.val()
@rest.login({email: email, password: password, remember_me: true})
.done(@onLoginDone)
.fail(@onLoginFail)
onLoginDone: () =>
@signinBtn.removeClass('disabled')
$(this).triggerHandler(@EVENTS.CHECKOUT_SIGNED_IN, {})
onLoginFail: (jqXHR) =>
@signinBtn.removeClass('disabled')
if jqXHR.status == 422
@signinForm.addClass('login-error')
else
@app.notifyServerError(jqXHR, "Unable to log in. Try again later.")
onSkipSignin: () =>
$(this).triggerHandler(@EVENTS.CHECKOUT_SKIP_SIGN_IN, {})

View File

@ -0,0 +1,46 @@
(function(context,$) {
"use strict";
context.JK = context.JK || {};
context.JK.JamtrackLicenseDialog = function(app) {
var logger = context.JK.logger;
var $dialog = null;
var dialogId = 'jamtrack-license-dialog';
function beforeShow(data) {
}
function afterShow(data) {
}
function afterHide() {
}
function showDialog() {
return app.layout.showDialog(dialogId);
}
function events() {
}
function initialize() {
var dialogBindings = {
'beforeShow' : beforeShow,
'afterShow' : afterShow,
'afterHide': afterHide
};
app.bindDialog(dialogId, dialogBindings);
$dialog = $('[layout-id="' + dialogId + '"]');
events();
}
this.initialize = initialize;
this.showDialog = showDialog;
};
return this;
})(window,jQuery);

View File

@ -0,0 +1,61 @@
$ = jQuery
context = window
context.JK ||= {}
context.JK.JamtrackPaymentHistoryDialog = class JamtrackPaymentHistoryDialog
constructor: (@app) ->
@rest = context.JK.Rest()
@client = context.jamClient
@logger = context.JK.logger
@screen = null
@dialogId = 'jamtrack-payment-history-dialog';
@dialog = null;
initialize:() =>
dialogBindings = {
'beforeShow' : @beforeShow,
'afterShow' : @afterShow
}
@dialog = $('[layout-id="' + @dialogId + '"]');
@app.bindDialog(@dialogId, dialogBindings);
@tbody = @dialog.find("table.payment-table tbody")
@rowTemplate = $('#template-payment-history-row').html()
beforeShow:() =>
# Get payment history from jamrest
payments = [
{date: new Date(2013, 4, 5), amount: 372.33},
{date: new Date(2014, 5, 5), amount: 338.44}
]
@rest.getPaymentHistory()
.done(@showPaymentHistory)
.fail(@app.ajaxError)
showPaymentHistory:(data) =>
# Turn in to HTML rows and append:
@tbody.html("")
if data.payments? && data.payments.length > 0
for p in data.payments
amt = p.amount
amt = 0 if !amt?
payment = {
date: context.JK.formatDateYYYYMMDD(p.created_at)
amount: (amt * 100).toFixed(2)
status: p.status
payment_method: p.payment_method.replace("_", " ")
reference: p.reference
}
tr = $(context._.template(@rowTemplate, payment, { variable: 'data' }));
@tbody.append(tr);
else
tr = "<tr><td colspan='5'>No payments found</td></tr>"
@tbody.append(tr);
afterShow:() =>
showDialog:() =>
@app.layout.showDialog(@dialogId)

View File

@ -0,0 +1,45 @@
(function(context,$) {
"use strict";
context.JK = context.JK || {};
context.JK.LoginRequiredDialog = function(app) {
var logger = context.JK.logger;
var $dialog = null;
var dialogId = 'login-required-dialog';
function beforeShow(data) {
}
function afterShow(data) {
}
function afterHide() {
}
function events() {
$dialog.find('.go-to-jamtracks').click(function() {
app.layout.closeDialog(dialogId)
context.location.href = $(this).attr('href')
})
}
function initialize() {
var dialogBindings = {
'beforeShow' : beforeShow,
'afterShow' : afterShow,
'afterHide': afterHide
};
app.bindDialog(dialogId, dialogBindings);
$dialog = $('#' + dialogId);
events();
}
this.initialize = initialize;
};
return this;
})(window,jQuery);

View File

@ -127,6 +127,9 @@
var clientPreferencesDialog = new JK.ClientPreferencesDialog(app);
clientPreferencesDialog.initialize();
var loginRequiredDialog = new JK.LoginRequiredDialog(app);
loginRequiredDialog.initialize();
}
// wait 10 seconds
@ -201,8 +204,10 @@
var user = app.user()
if(user) {
user.done(function(userProfile) {
console.log("app.layout.getCurrentScreen() != 'checkoutOrderScreen'", app.layout.getCurrentScreen())
if (userProfile.show_whats_next &&
window.location.pathname.indexOf(gon.client_path) == 0 &&
window.location.pathname.indexOf('/checkout') == -1 &&
!app.layout.isDialogShowing('getting-started'))
{
app.layout.showDialog('getting-started');
@ -212,13 +217,7 @@
}
function initShoppingCart(app) {
var user = app.user()
if(user) {
user.done(function(userProfile) {
context.JK.JamTrackUtils.checkShoppingCart();
})
}
context.JK.JamTrackUtils.checkShoppingCart();
}
})(window, jQuery);

View File

@ -739,7 +739,7 @@
}
function SessionOpenMetronome(bpm, click, meter, mode){
console.log("Setting metronome BPM: ", bpm)
logger.debug("Setting metronome BPM: ", bpm)
metronomeActive =true
metronomeBPM = bpm
metronomeSound = click

View File

@ -57,9 +57,11 @@
})
.fail(function(xhr, textStatus, errorMessage) {
if (xhr.status === 404) {
logger.warn("unable to list active sessions (404)")
// swallow 404
}
else {
logger.warn("unable to list active sessions")
app.ajaxError(xhr, textStatus, errorMessage);
}
})

View File

@ -27,6 +27,9 @@
CHAT: "1"
};
context.JK.AVAILABILITY_US = "United States";
context.JK.MASTER_TRACK = "Master";
context.JK.EVENTS = {
DIALOG_CLOSED : 'dialog_closed',
SHOW_SIGNUP : 'show_signup',
@ -47,7 +50,9 @@
CONNECTION_DOWN: 'connection_down',
SCREEN_CHANGED: 'screen_changed',
JAMTRACK_DOWNLOADER_STATE_CHANGED: 'jamtrack_downloader_state',
METRONOME_PLAYBACK_MODE_SELECTED: 'metronome_playback_mode_selected'
METRONOME_PLAYBACK_MODE_SELECTED: 'metronome_playback_mode_selected',
CHECKOUT_SIGNED_IN: 'checkout_signed_in',
CHECKOUT_SKIP_SIGN_IN: 'checkout_skip_sign_in'
};
context.JK.PLAYBACK_MONITOR_MODE = {

View File

@ -6,6 +6,7 @@
context.JK.HomeScreen = function(app) {
var logger = context.JK.logger;
var isFtueComplete = false;
var $screen = null;
function beforeShow(data) {
}
@ -86,9 +87,20 @@
var screenBindings = { 'beforeShow': beforeShow };
app.bindScreen('home', screenBindings);
events();
$screen = $('.screen[layout-id="home"]')
$('.profile').on('click', function() {
$screen.find('.profile').on('click', function() {
var $destination = $('[layout-id="profile"]');
if(!context.JK.currentUserId && !$destination.is('.no-login-required')) {
// if there is no user and login is required, then stop user from clicknig through
app.layout.showDialog('login-required-dialog')
}
else
{
context.location = '/client#/profile/' + context.JK.currentUserId;
}
});
};

View File

@ -473,7 +473,7 @@
processData: false,
contentType: 'application/json',
data: JSON.stringify(options)
});
})
}
function getUserDetail(options) {
@ -1236,6 +1236,15 @@
})
}
function playJamTrack(jamTrackId) {
return $.ajax({
type: "POST",
url: '/api/jamtracks/played/' + jamTrackId,
dataType: "json",
contentType: 'application/json'
});
}
function closeJamTrack(options) {
var musicSessionId = options["id"];
delete options["id"];
@ -1479,6 +1488,15 @@
});
}
function getPaymentHistory(options) {
return $.ajax({
type: "GET",
url: '/api/recurly/payment_history',
dataType: "json",
contentType: 'application/json'
});
}
function getBackingTracks(options) {
return $.ajax({
type: "GET",
@ -1561,10 +1579,10 @@
});
}
function placeOrder(options) {
function placeOrder() {
return $.ajax({
type: "POST",
url: '/api/recurly/place_order?' + $.param(options),
url: '/api/recurly/place_order',
dataType: "json",
contentType: 'application/json'
});
@ -1753,6 +1771,7 @@
this.updateAudioLatency = updateAudioLatency;
this.getJamtracks = getJamtracks;
this.getPurchasedJamTracks = getPurchasedJamTracks;
this.getPaymentHistory = getPaymentHistory;
this.getJamTrackRight = getJamTrackRight;
this.enqueueJamTrack = enqueueJamTrack;
this.getBackingTracks = getBackingTracks;
@ -1774,6 +1793,7 @@
this.addRecordingTimeline = addRecordingTimeline;
this.getMusicianSearchFilter = getMusicianSearchFilter;
this.postMusicianSearchFilter = postMusicianSearchFilter;
this.playJamTrack = playJamTrack;
return this;
};

View File

@ -0,0 +1,242 @@
$ = jQuery
context = window
context.JK ||= {}
context.JK.JamTrackScreen=class JamTrackScreen
LIMIT = 10
instrument_logo_map = context.JK.getInstrumentIconMap24()
constructor: (@app) ->
@logger = context.JK.logger
@screen = null
@content = null
@scroller = null
@genre = null
@artist = null
@instrument = null
@availability = null
@nextPager = null
@noMoreJamtracks = null
@currentPage = 0
@next = null
@currentQuery = this.defaultQuery()
@expanded = false
beforeShow:(data) =>
this.setFilterFromURL()
this.refresh()
afterShow:(data) =>
events:() =>
@genre.on 'change', this.search
@artist.on 'change', this.search
@instrument.on 'change', this.search
@availability.on 'change', this.search
clearResults:() =>
@currentPage = 0
@content.empty()
@noMoreJamtracks.hide()
@next = null
setFilterFromURL:() =>
# Grab parms from URL for artist, instrument, and availability
parms=this.getParams()
if(parms.artist?)
@artist.val(parms.artist)
if(parms.instrument?)
@instrument.val(parms.instrument)
if(parms.availability?)
@availability.val(parms.availability)
window.history.replaceState({}, "", "/client#/jamtrack")
getParams:() =>
params = {}
q = window.location.href.split("?")[1]
if q?
q = q.split('#')[0]
raw_vars = q.split("&")
for v in raw_vars
[key, val] = v.split("=")
params[key] = decodeURIComponent(val)
ms
params
refresh:() =>
@currentQuery = this.buildQuery()
that = this
rest.getJamtracks(@currentQuery).done((response) ->
that.clearResults()
that.handleJamtrackResponse(response)
).fail (jqXHR) ->
that.clearResults()
that.noMoreJamtracks.show()
that.app.notifyServerError jqXHR, 'Jamtrack Unavailable'
search:() =>
this.refresh()
false
defaultQuery:() =>
query =
per_page: LIMIT
page: @currentPage+1
if @next
query.since = @next
query
buildQuery:() =>
@currentQuery = this.defaultQuery()
# genre filter
# var genres = @screen.find('#jamtrack_genre').val()
# if (genres !== undefined) {
# @currentQuery.genre = genres
# }
# instrument filter
instrument = @instrument.val()
if instrument?
@currentQuery.instrument = instrument
# artist filter
art = @artist.val()
if art?
@currentQuery.artist = art
# availability filter
availability = @availability.val()
if availability?
@currentQuery.availability = availability
@currentQuery
handleJamtrackResponse:(response) =>
@next = response.next
this.renderJamtracks(response)
if response.next == null
# if we less results than asked for, end searching
@scroller.infinitescroll 'pause'
if @currentPage == 0 and response.jamtracks.length == 0
@content.append '<div class=\'no-jamtracks-msg\'>There\'s no jamtracks.</div>'
if @currentPage > 0
@noMoreJamtracks.show()
# there are bugs with infinitescroll not removing the 'loading'.
# it's most noticeable at the end of the list, so whack all such entries
$('.infinite-scroll-loader').remove()
else
@currentPage++
this.buildQuery()
this.registerInfiniteScroll()
registerInfiniteScroll:() =>
@scroller.infinitescroll {
behavior: 'local'
navSelector: '#jamtrackScreen .btn-next-pager'
nextSelector: '#jamtrackScreen .btn-next-pager'
binder: @scroller
dataType: 'json'
appendCallback: false
prefill: false
bufferPx: 100
loading:
msg: $('<div class="infinite-scroll-loader">Loading ...</div>')
img: '/assets/shared/spinner.gif'
path: (page) ->
'/api/jamtracks?' + $.param(this.buildQuery())
}, (json, opts) ->
this.handleJamtrackResponse(json)
@scroller.infinitescroll 'resume'
playJamtrack:(e) =>
e.preventDefault()
addToCartJamtrack:(e) =>
e.preventDefault()
params = id: $(e.target).attr('data-jamtrack-id')
rest.addJamtrackToShoppingCart(params).done((response) ->
context.location = '/client#/shoppingCart'
).fail @app.ajaxError
licenseUSWhy:(e) =>
e.preventDefault()
@app.layout.showDialog 'jamtrack-availability-dialog'
registerEvents:() =>
@screen.find('.jamtrack-detail-btn').on 'click', this.showJamtrackDescription
@screen.find('.play-button').on 'click', this.playJamtrack
@screen.find('.jamtrack-add-cart').on 'click', this.addToCartJamtrack
@screen.find('.license-us-why').on 'click', this.licenseUSWhy
@screen.find('.jamtrack-detail-btn').on 'click', this.toggleExpanded
renderJamtracks:(data) =>
that = this
for jamtrack in data.jamtracks
for track in jamtrack.tracks
continue if track.track_type=='Master'
inst = '../assets/content/icon_instrument_default24.png'
if track.instrument.id in instrument_logo_map
inst = instrument_logo_map[track.instrument.id].asset
track.instrument_url = inst
track.instrument_desc = track.instrument.description
if track.part != ''
track.instrument_desc += ' (' + track.part + ')'
options =
jamtrack: jamtrack
expanded: that.expanded
@jamtrackItem = $(context._.template($('#template-jamtrack').html(), options, variable: 'data'))
that.renderJamtrack(@jamtrackItem)
this.registerEvents()
showJamtrackDescription:(e) =>
e.preventDefault()
@description = $(e.target).parent('.detail-arrow').next()
if @description.css('display') == 'none'
@description.show()
else
@description.hide()
toggleExpanded:() =>
this.expanded = !this.expanded
this.refresh()
renderJamtrack:(jamtrack) =>
@content.append jamtrack
initialize:() =>
screenBindings =
'beforeShow': this.beforeShow
'afterShow': this.afterShow
@app.bindScreen 'jamtrack', screenBindings
@screen = $('#jamtrack-find-form')
@scroller = @screen.find('.content-body-scroller')
@content = @screen.find('.jamtrack-content')
@genre = @screen.find('#jamtrack_genre')
@artist = @screen.find('#jamtrack_artist')
@instrument = @screen.find('#jamtrack_instrument')
@availability = @screen.find('#jamtrack_availability')
@nextPager = @screen.find('a.btn-next-pager')
@noMoreJamtracks = @screen.find('.end-of-jamtrack-list')
if @screen.length == 0
throw new Error('@screen must be specified')
if @scroller.length == 0
throw new Error('@scroller must be specified')
if @content.length == 0
throw new Error('@content must be specified')
if @noMoreJamtracks.length == 0
throw new Error('@noMoreJamtracks must be specified')
#if(@genre.length == 0) throw new Error("@genre must be specified")
if @artist.length == 0
throw new Error('@artist must be specified')
if @instrument.length == 0
throw new Error('@instrument must be specified')
if @availability.length == 0
throw new Error('@availability must be specified')
this.events()

View File

@ -16,13 +16,18 @@ class JamTrackUtils
@rest.getShoppingCarts().done(this.displayCartIcon)
displayCartIcon: (carts) =>
cartLink = $("a[href='" + "/client#/shoppingCart" + "']")
cartLink = $("#header-shopping-cart")
if carts.length > 0
cartLink.removeClass("hidden")
else
cartLink.addClass("hidden")
compareAddress: (billing, shipping) =>
billing.address1 == shipping.address1 &&
billing.address2 == shipping.address2 &&
billing.zip == shipping.zip &&
billing.city == shipping.city &&
billing.country == shipping.country;
# global instance

View File

@ -132,6 +132,9 @@
logger.error("Unexpected ajax error: " + textStatus + ", msg:" + errorMessage);
app.notify({title: "Oops!", text: "What you were looking for is gone now."});
}
else if(jqXHR.status === 403) {
logger.debug("not logged in");
}
else if (jqXHR.status === 422) {
logger.error("Unexpected ajax error: " + textStatus + ", msg: " + errorMessage + ", response: " + jqXHR.responseText);
// present a nicer message
@ -282,20 +285,37 @@
var hash = context.location.hash;
var screen = 'home'
try {
context.RouteMap.parse(hash);
var location = context.RouteMap.parse(hash);
screen = location.page.substring(1); // remove leading slash
}
catch (e) {
logger.debug("ignoring bogus screen name: %o", hash)
hash = null;
}
var url = '/client#/home';
var $destination = $('[layout-id="' + screen + '"]');
if(!context.JK.currentUserId && !$destination.is('.no-login-required')) {
logger.debug("not logged in so redirected to login from screen: " + screen)
var redirectPath= '?redirect-to=' + encodeURIComponent(JK.locationPath());
if(gon.isNativeClient) {
window.location.href = '/signin' + redirectPath;
}
else {
window.location.href = '/' + redirectPath;
}
return;
}
var url = '/client#/' + screen;
if (hash) {
url = hash;
}
logger.debug("Changing screen to " + url);
logger.debug("jamkazam: Changing screen to " + url + " (hash=" + hash + ")") ;
context.location = url;
}
@ -377,7 +397,11 @@
app.notify({title: "Unable to Load User", text: "You should reload the page."})
});
}
} // if userDeferred
}
else {
userDeferred = new $.Deferred();
userDeferred.reject('not_logged_in');
}
$(document).triggerHandler('JAMKAZAM_READY', {app:app})

View File

@ -1,263 +0,0 @@
(function(context,$) {
"use strict";
context.JK = context.JK || {};
context.JK.JamTrackScreen = function(app) {
var logger = context.JK.logger;
var $screen = null;
var $content = null;
var $scroller = null;
var $genre = null;
var $instrument = null;
var $availability = null;
var $nextPager = null;
var $noMoreJamtracks = null;
var currentQuery = defaultQuery();
var currentPage = 0;
var LIMIT = 10;
var next = null;
var instrument_logo_map = context.JK.getInstrumentIconMap24();
function beforeShow(data) {
refresh();
}
function afterShow(data) {
}
function events() {
$genre.on("change", search);
$instrument.on("change", search);
$availability.on("change", search);
}
function clearResults() {
//logger.debug("CLEARING CONTENT")
currentPage = 0;
$content.empty();
$noMoreJamtracks.hide();
next = null;
}
function refresh() {
currentQuery = buildQuery();
rest.getJamtracks(currentQuery)
.done(function(response) {
clearResults();
handleJamtrackResponse(response);
})
.fail(function(jqXHR) {
clearResults();
$noMoreJamtracks.show();
app.notifyServerError(jqXHR, 'Jamtrack Unavailable')
})
}
function search() {
logger.debug("Searching for jamtracks...");
refresh();
return false;
}
function defaultQuery() {
var query = { limit:LIMIT, page:currentPage};
if(next) {
query.since = next;
}
return query;
}
function buildQuery() {
currentQuery = defaultQuery();
// genre filter
var genres = $screen.find('#jamtrack_genre').val();
if (genres !== undefined) {
currentQuery.genre = genres;
}
// instrument filter
var instrument = $instrument.val();
if (instrument !== undefined) {
currentQuery.instrument = instrument;
}
// availability filter
var availability = $availability.val();
if (availability !== undefined) {
currentQuery.availability = availability;
}
return currentQuery;
}
function handleJamtrackResponse(response) {
//logger.debug("Handling response", JSON.stringify(response))
next = response.next;
renderJamtracks(response);
if(response.next == null) {
// if we less results than asked for, end searching
$scroller.infinitescroll('pause');
logger.debug("end of jamtracks");
if(currentPage == 0 && response.jamtracks.length == 0) {
$content.append("<div class='no-jamtracks-msg'>There's no jamtracks.</div>") ;
}
if(currentPage > 0) {
$noMoreJamtracks.show();
// there are bugs with infinitescroll not removing the 'loading'.
// it's most noticeable at the end of the list, so whack all such entries
$('.infinite-scroll-loader').remove();
}
}
else {
currentPage++;
buildQuery();
registerInfiniteScroll();
}
}
function registerInfiniteScroll() {
$scroller.infinitescroll({
behavior: 'local',
navSelector: '#jamtrackScreen .btn-next-pager',
nextSelector: '#jamtrackScreen .btn-next-pager',
binder: $scroller,
dataType: 'json',
appendCallback: false,
prefill: false,
bufferPx: 100,
loading: {
msg: $('<div class="infinite-scroll-loader">Loading ...</div>'),
img: '/assets/shared/spinner.gif'
},
path: function(page) {
return '/api/jamtracks?' + $.param(buildQuery());
}
},function(json, opts) {
handleJamtrackResponse(json);
});
$scroller.infinitescroll('resume');
}
function playJamtrack(e) {
e.preventDefault();
}
function addToCartJamtrack(e) {
e.preventDefault();
var params = {id: $(e.target).attr("data-jamtrack-id")};
rest.addJamtrackToShoppingCart(params)
.done(function(response) {
context.location = "/client#/shoppingCart";
})
.fail(app.ajaxError);
}
function licenseUSWhy(e) {
e.preventDefault();
app.layout.showDialog('jamtrack-availability-dialog');
}
function registerEvents() {
$screen.find('.jamtrack-detail-btn').on("click", showJamtrackDescription);
$screen.find('.play-button').on('click', playJamtrack);
$screen.find('.jamtrack-add-cart').on('click', addToCartJamtrack);
$screen.find('.license-us-why').on('click', licenseUSWhy);
}
function renderJamtracks(data) {
$.each(data.jamtracks, function(i, jamtrack) {
$.each(jamtrack.tracks, function (index, track) {
if(track.track_type == 'Master') {
return; // continue
}
var inst = '../assets/content/icon_instrument_default24.png';
if (track.instrument.id in instrument_logo_map) {
inst = instrument_logo_map[track.instrument.id].asset;
}
track.instrument_url = inst;
track.instrument_desc = track.instrument.description;
if (track.part != "") {
track.instrument_desc += " ( " + track.part + " )";
}
});
var options = {
jamtrack: jamtrack
};
var $jamtrackItem = $(
context._.template(
$('#template-jamtrack').html(),
options,
{variable: 'data'}
)
);
renderJamtrack($jamtrackItem );
});
registerEvents();
}
function showJamtrackDescription(e) {
e.preventDefault();
var $description = $(e.target).parent(".detail-arrow").next();
if ($description.css("display") == "none") {
$description.show();
}
else {
$description.hide();
}
}
function renderJamtrack(jamtrack) {
$content.append(jamtrack);
}
function initialize() {
var screenBindings = {
'beforeShow': beforeShow,
'afterShow': afterShow
};
app.bindScreen('jamtrack', screenBindings);
$screen = $("#jamtrack-find-form");
$scroller = $screen.find('.content-body-scroller');
$content = $screen.find(".jamtrack-content");
$genre = $screen.find("#jamtrack_genre");
$instrument = $screen.find("#jamtrack_instrument");
$availability = $screen.find("#jamtrack_availability");
$nextPager = $screen.find("a.btn-next-pager");
$noMoreJamtracks = $screen.find("#end-of-jamtrack-list");
if($screen.length == 0) throw "$screen must be specified";
if($scroller.length == 0) throw "$scroller must be specified";
if($content.length == 0) throw "$content must be specified";
if($noMoreJamtracks.length == 0) throw "$noMoreJamtracks must be specified";
if($genre.length == 0) throw "$genre must be specified";
if($instrument.length == 0) throw "$instrument must be specified";
if($availability.length ==0) throw "$availability must be specified";
events();
}
this.initialize = initialize;
return this;
}
})(window,jQuery);

View File

@ -0,0 +1,243 @@
$ = jQuery
context = window
context.JK ||= {}
context.JK.JamTrackScreen=class JamTrackScreen
LIMIT = 10
instrument_logo_map = context.JK.getInstrumentIconMap24()
constructor: (@app) ->
@logger = context.JK.logger
@screen = null
@content = null
@scroller = null
@genre = null
@artist = null
@instrument = null
@availability = null
@nextPager = null
@noMoreJamtracks = null
@currentPage = 0
@next = null
@currentQuery = this.defaultQuery()
@expanded = false
beforeShow:(data) =>
this.setFilterFromURL()
this.refresh()
afterShow:(data) =>
events:() =>
@genre.on 'change', this.search
@artist.on 'change', this.search
@instrument.on 'change', this.search
@availability.on 'change', this.search
clearResults:() =>
#$logger.debug("CLEARING CONTENT")
@currentPage = 0
@content.empty()
@noMoreJamtracks.hide()
@next = null
setFilterFromURL:() =>
# Grab parms from URL for artist, instrument, and availability
parms=this.getParams()
this.logger.debug("parms", parms)
if(parms.artist?)
@artist.val(parms.artist)
if(parms.instrument?)
@instrument.val(parms.instrument)
if(parms.availability?)
@availability.val(parms.availability)
window.history.replaceState({}, "", "/client#/jamtrack")
getParams:() =>
params = {}
q = window.location.href.split("?")[1]
if q?
q = q.split('#')[0]
raw_vars = q.split("&")
for v in raw_vars
[key, val] = v.split("=")
params[key] = decodeURIComponent(val)
params
refresh:() =>
@currentQuery = this.buildQuery()
that = this
rest.getJamtracks(@currentQuery).done((response) ->
that.clearResults()
that.handleJamtrackResponse(response)
).fail (jqXHR) ->
that.clearResults()
that.noMoreJamtracks.show()
that.app.notifyServerError jqXHR, 'Jamtrack Unavailable'
search:() =>
this.refresh()
false
defaultQuery:() =>
query =
per_page: LIMIT
page: @currentPage+1
if @next
query.since = @next
query
buildQuery:() =>
@currentQuery = this.defaultQuery()
# genre filter
# var genres = @screen.find('#jamtrack_genre').val()
# if (genres !== undefined) {
# @currentQuery.genre = genres
# }
# instrument filter
instrument = @instrument.val()
if instrument?
@currentQuery.instrument = instrument
# artist filter
art = @artist.val()
if art?
@currentQuery.artist = art
# availability filter
availability = @availability.val()
if availability?
@currentQuery.availability = availability
@currentQuery
handleJamtrackResponse:(response) =>
#logger.debug("Handling response", JSON.stringify(response))
@next = response.next
this.renderJamtracks(response)
if response.next == null
# if we less results than asked for, end searching
@scroller.infinitescroll 'pause'
if @currentPage == 0 and response.jamtracks.length == 0
@content.append '<div class=\'no-jamtracks-msg\'>There\'s no jamtracks.</div>'
if @currentPage > 0
@noMoreJamtracks.show()
# there are bugs with infinitescroll not removing the 'loading'.
# it's most noticeable at the end of the list, so whack all such entries
$('.infinite-scroll-loader').remove()
else
@currentPage++
this.buildQuery()
this.registerInfiniteScroll()
registerInfiniteScroll:() =>
@scroller.infinitescroll {
behavior: 'local'
navSelector: '#jamtrackScreen .btn-next-pager'
nextSelector: '#jamtrackScreen .btn-next-pager'
binder: @scroller
dataType: 'json'
appendCallback: false
prefill: false
bufferPx: 100
loading:
msg: $('<div class="infinite-scroll-loader">Loading ...</div>')
img: '/assets/shared/spinner.gif'
path: (page) ->
'/api/jamtracks?' + $.param(this.buildQuery())
}, (json, opts) ->
this.handleJamtrackResponse(json)
@scroller.infinitescroll 'resume'
playJamtrack:(e) =>
e.preventDefault()
addToCartJamtrack:(e) =>
e.preventDefault()
params = id: $(e.target).attr('data-jamtrack-id')
rest.addJamtrackToShoppingCart(params).done((response) ->
context.location = '/client#/shoppingCart'
).fail @app.ajaxError
licenseUSWhy:(e) =>
e.preventDefault()
@app.layout.showDialog 'jamtrack-availability-dialog'
registerEvents:() =>
@screen.find('.jamtrack-detail-btn').on 'click', this.showJamtrackDescription
@screen.find('.play-button').on 'click', this.playJamtrack
@screen.find('.jamtrack-add-cart').on 'click', this.addToCartJamtrack
@screen.find('.license-us-why').on 'click', this.licenseUSWhy
@screen.find('.jamtrack-detail-btn').on 'click', this.toggleExpanded
renderJamtracks:(data) =>
that = this
for jamtrack in data.jamtracks
for track in jamtrack.tracks
continue if track.track_type=='Master'
inst = '../assets/content/icon_instrument_default24.png'
if track.instrument.id in instrument_logo_map
inst = instrument_logo_map[track.instrument.id].asset
track.instrument_url = inst
track.instrument_desc = track.instrument.description
if track.part != ''
track.instrument_desc += ' (' + track.part + ')'
options =
jamtrack: jamtrack
expanded: that.expanded
@jamtrackItem = $(context._.template($('#template-jamtrack').html(), options, variable: 'data'))
that.renderJamtrack(@jamtrackItem)
this.registerEvents()
showJamtrackDescription:(e) =>
e.preventDefault()
@description = $(e.target).parent('.detail-arrow').next()
if @description.css('display') == 'none'
@description.show()
else
@description.hide()
toggleExpanded:() =>
this.expanded = !this.expanded
this.refresh()
renderJamtrack:(jamtrack) =>
@content.append jamtrack
initialize:() =>
screenBindings =
'beforeShow': this.beforeShow
'afterShow': this.afterShow
@app.bindScreen 'jamtrack', screenBindings
@screen = $('#jamtrack-find-form')
@scroller = @screen.find('.content-body-scroller')
@content = @screen.find('.jamtrack-content')
@genre = @screen.find('#jamtrack_genre')
@artist = @screen.find('#jamtrack_artist')
@instrument = @screen.find('#jamtrack_instrument')
@availability = @screen.find('#jamtrack_availability')
@nextPager = @screen.find('a.btn-next-pager')
@noMoreJamtracks = @screen.find('.end-of-jamtrack-list')
if @screen.length == 0
throw new Error('@screen must be specified')
if @scroller.length == 0
throw new Error('@scroller must be specified')
if @content.length == 0
throw new Error('@content must be specified')
if @noMoreJamtracks.length == 0
throw new Error('@noMoreJamtracks must be specified')
#if(@genre.length == 0) throw new Error("@genre must be specified")
if @artist.length == 0
throw new Error('@artist must be specified')
if @instrument.length == 0
throw new Error('@instrument must be specified')
if @availability.length == 0
throw new Error('@availability must be specified')
this.events()

View File

@ -0,0 +1,53 @@
$ = jQuery
context = window
context.JK ||= {}
context.JK.JamTrackLanding = class JamTrackLanding
constructor: (@app) ->
@rest = context.JK.Rest()
@client = context.jamClient
@logger = context.JK.logger
@screen = null
initialize:() =>
screenBindings =
'beforeShow': @beforeShow
'afterShow': @afterShow
@app.bindScreen('jamtrackLanding', screenBindings)
@screen = $('#jamtrackLanding')
beforeShow:() =>
# Get artist names and build links
@rest.getJamtracks({group_artist: true})
.done(this.buildArtistLinks)
.fail(this.handleFailure)
# Bind links to action that will open the jam_tracks list view filtered to given artist_name:
# artist_name
this.bindArtistLinks()
afterShow:() =>
buildArtistLinks:(response) =>
# Get artist names and build links
jamtracks = response.jamtracks
$("#band_list>li:not('#no_bands_found')").remove()
if jamtracks.length==0
$("#no_bands_found").removeClass("hidden")
else
$("#no_bands_found").addClass("hidden")
# client#/jamtrack
for jamtrack in jamtracks
artistLink = "<a href='client?artist=#{encodeURIComponent(jamtrack.original_artist)}#/jamtrack' class='artist-link' artist='#{jamtrack.original_artist}'>#{jamtrack.original_artist}</a>"
$("#band_list").append("<li>#{artistLink}</li>")
# We don't want to do a full page load if this is clicked on here:
bindArtistLinks:() =>
band_list=$("ul#band_list")
that=this
band_list.on "click", "a.artist-link", (event)->
context.location="client#/jamtrack"
window.history.replaceState({}, "", this.href)
event.preventDefault()
handleFailure:(error) =>

View File

@ -95,6 +95,8 @@
layoutHeader(width, height);
layoutNotify(width, height);
layoutFooter(width, height);
$(document).triggerHandler('layout_resized');
}
function layoutCurtain(screenWidth, screenHeight) {
@ -407,8 +409,15 @@
}
var destination = $(evt.currentTarget).attr('layout-link');
var destinationType = $('[layout-id="' + destination + '"]').attr("layout");
var $destination = $('[layout-id="' + destination + '"]');
var destinationType = $destination.attr("layout");
if (destinationType === "screen") {
if(!context.JK.currentUserId && !$destination.is('.no-login-required')) {
// there is no user, and this item does not support 'no-login', so warn user
showDialog('login-required-dialog');
return;
}
context.location = '/client#/' + destination;
} else if (destinationType === "dialog") {
showDialog(destination);
@ -540,7 +549,7 @@
var accepted = screenEvent(previousScreen, 'beforeHide', data);
if(accepted === false) return;
logger.debug("Changing screen to " + currentScreen);
logger.debug("layout: changing screen to " + currentScreen);
$(document).triggerHandler(EVENTS.SCREEN_CHANGED, {previousScreen: previousScreen, newScreen: currentScreen})
@ -687,6 +696,7 @@
return null;
}
logger.debug("opening dialog: " + dialog)
var $overlay = $('.dialog-overlay')
if (opts.sizeOverlayToContent) {
@ -719,6 +729,12 @@
function panelHeaderClicked(evt) {
evt.preventDefault();
if(!context.JK.currentUserId) {
showDialog('login-required-dialog');
return false;
}
expandedPanel = $(evt.currentTarget).closest('[layout="panel"]').attr("layout-id");
layout();
return false;

View File

@ -213,7 +213,7 @@
})
.fail(function() {
isLoading = false;
app.ajaxError();
app.ajaxError(arguments);
})
}
@ -1375,7 +1375,9 @@
events();
populate();
app.user().done(function(){
populate();
})
};
this.initialize = initialize;

View File

@ -42,6 +42,7 @@
step = 2;
renderNavigation();
renderAccountInfo();
$("#order_error").addClass("hidden")
}
function resetJamTrackDownloadInfo() {
@ -115,6 +116,7 @@
purchasedJamTrackIterator = 0;
}
// TODO: Refactor: this function is long and fraught with many return points.
function next(e) {
e.preventDefault();
$("#order_error").addClass("hidden")
@ -308,10 +310,8 @@
$paymentMethod.find('#divCardName .error-text').remove();
$paymentMethod.find('#divCardName').addClass("error");
$paymentMethod.find('#card-name').after("<ul class='error-text'><li>Card Name is required</li></ul>");
return false;
}
else {
} else {
$paymentMethod.find('#divCardName').removeClass("error");
}
@ -319,21 +319,37 @@
$paymentMethod.find('#divCardNumber .error-text').remove();
$paymentMethod.find('#divCardNumber').addClass("error");
$paymentMethod.find('#card-number').after("<ul class='error-text'><li>Card Number is required</li></ul>");
return false;
}
else {
} else if (!$.payment.validateCardNumber(card_number)) {
$paymentMethod.find('#divCardNumber .error-text').remove();
$paymentMethod.find('#divCardNumber').addClass("error");
$paymentMethod.find('#card-number').after("<ul class='error-text'><li>Card Number is not valid</li></ul>");
return false;
} else {
$paymentMethod.find('#divCardNumber').removeClass("error");
}
if (!$.payment.validateCardExpiry(card_month, card_year)) {
$paymentMethod.find('#divCardExpiry .error-text').remove();
$paymentMethod.find('#divCardExpiry').addClass("error");
$paymentMethod.find('#card-expiry').after("<ul class='error-text'><li>Card Number is not valid</li></ul>");
} else {
$paymentMethod.find('#divCardExpiry').removeClass("error");
}
if (!card_verify) {
$paymentMethod.find('#divCardVerify .error-text').remove();
$paymentMethod.find('#divCardVerify').addClass("error");
$paymentMethod.find('#card_verify').after("<ul class='error-text'><li>Card Verification Value is required</li></ul>");
$paymentMethod.find('#card-verify').after("<ul class='error-text'><li>Card Verification Value is required</li></ul>");
return false;
}
else {
} else if(!$.payment.validateCardCVC(card_verify)) {
$paymentMethod.find('#divCardVerify .error-text').remove();
$paymentMethod.find('#divCardVerify').addClass("error");
$paymentMethod.find('#card-verify').after("<ul class='error-text'><li>Card Verification Value is not valid.</li></ul>");
return false;
} else {
$paymentMethod.find('#divCardVerify').removeClass("error");
}
@ -607,6 +623,10 @@
radioClass: 'iradio_minimal',
inheritClass: true
});
// Use jquery.payment to limit characters and length:
$paymentMethod.find("#card-number").payment('formatCardNumber');
$paymentMethod.find("#card-verify").payment('formatCardCVC');
}
function initialize() {

View File

@ -104,10 +104,10 @@
$playButton.on('click', function(e) {
var sessionModel = context.JK.CurrentSessionModel || null;
if(sessionModel && sessionModel.areControlsLockedForJamTrackRecording() && $parentElement.closest('.session-track').data('track_data').type == 'jam_track') {
context.JK.prodBubble($fader, 'jamtrack-controls-disabled', {}, {positions:['top'], offsetParent: $playButton})
return false;
}
//if(sessionModel && sessionModel.areControlsLockedForJamTrackRecording() && $parentElement.closest('.session-track').data('track_data').type == 'jam_track') {
// context.JK.prodBubble($fader, 'jamtrack-controls-disabled', {}, {positions:['top'], offsetParent: $playButton})
// return false;
//}
startPlay();
return false;
@ -115,10 +115,10 @@
$pauseButton.on('click', function(e) {
var sessionModel = context.JK.CurrentSessionModel || null;
if(sessionModel && sessionModel.areControlsLockedForJamTrackRecording() && $parentElement.closest('.session-track').data('track_data').type == 'jam_track') {
context.JK.prodBubble($pauseButton, 'jamtrack-controls-disabled', {}, {positions:['top'], offsetParent: $pauseButton})
return false;
}
//if(sessionModel && sessionModel.areControlsLockedForJamTrackRecording() && $parentElement.closest('.session-track').data('track_data').type == 'jam_track') {
// context.JK.prodBubble($pauseButton, 'jamtrack-controls-disabled', {}, {positions:['top'], offsetParent: $pauseButton})
// return false;
//}
stopPlay();
return false;

View File

@ -1054,7 +1054,7 @@
context.JK.GenreSelectorHelper.render('#create-session-genre');
inviteMusiciansUtil.loadFriends();
//inviteMusiciansUtil.loadFriends();
context.JK.dropdown($screen.find('#session-musician-access'));
context.JK.dropdown($screen.find('#session-fans-access'));

View File

@ -6,7 +6,7 @@
context.JK.SelectLocation = Class.extend({
init: function ($countries, $regions, $cities, app) {
init: function ($countries, $regions, $cities, app, useEasyDropdown) {
this.api = context.JK.Rest();
this.logger = context.JK.logger;
this.loadingCitiesData = false;
@ -14,9 +14,12 @@
this.loadingCountriesData = false;
this.nilOptionStr = '<option value=""></option>';
this.nilOptionText = 'n/a';
this.countriesLoaded = false;
this.$countries = $countries;
this.$regions = $regions;
this.$cities = $cities;
this.$deferred = null;
this.useEasyDropdown = useEasyDropdown === undefined ? true : useEasyDropdown;
this.app = app;
$countries.on('change', function (evt) {
@ -24,11 +27,24 @@
this.handleCountryChanged();
return false;
}.bind(this));
$regions.on('change', function (evt) {
evt.stopPropagation();
this.handleRegionChanged();
return false;
}.bind(this));
if($regions) {
$regions.on('change', function (evt) {
evt.stopPropagation();
this.handleRegionChanged();
return false;
}.bind(this));
}
},
selectCountry: function (country) {
if(this.useEasyDropdown) {
this.$countries.easyDropDown('select', country, true)
}
else {
this.$countries.val(country)
}
},
ready: function() {
return this.$deferred;
},
load: function (country, region, city) {
@ -42,13 +58,9 @@
country = 'US';
}
this.loadingCountriesData = true;
this.loadingRegionsData = true;
this.loadingCitiesData = true;
// make the 3 slower requests, which only matter if the user wants to affect their ISP or location
this.api.getCountries()
this.loadingCountriesData = true;
this.$deferred = this.api.getCountries()
.done(function (countriesx) {
this.populateCountriesx(countriesx["countriesx"], country);
}.bind(this))
@ -57,7 +69,9 @@
this.loadingCountriesData = false;
}.bind(this))
if (country) {
if (country && this.$regions) {
this.loadingRegionsData = true;
this.api.getRegions({ country: country })
.done(function (regions) {
this.populateRegions(regions["regions"], region);
@ -67,7 +81,8 @@
this.loadingRegionsData = false;
}.bind(this))
if (region) {
if (region && this.$cities) {
this.loadingCitiesData = true;
this.api.getCities({ country: country, region: region })
.done(function (cities) {
this.populateCities(cities["cities"], this.city)
@ -78,9 +93,15 @@
}.bind(this))
}
}
return this.$deferred;
},
handleCountryChanged: function () {
var selectedCountry = this.$countries.val()
if(!this.$regions) {
return;
}
var selectedRegion = this.$regions.val()
var cityElement = this.$cities
@ -144,7 +165,9 @@
else {
cityElement.children().remove();
cityElement.append($(this.nilOptionStr).text(this.nilOptionText));
context.JK.dropdown(cityElement);
if(this.useEasyDropdown) {
context.JK.dropdown(cityElement);
}
}
},
@ -159,7 +182,7 @@
if (!countryx.countrycode) return;
var option = $(this.nilOptionStr);
option.text(countryx.countryname);
option.text(countryx.countryname ? countryx.countryname : countryx.countrycode);
option.attr("value", countryx.countrycode);
if (countryx.countrycode == this.country) {
@ -170,6 +193,8 @@
},
populateCountriesx: function (countriesx) {
this.countriesLoaded = true;
// countriesx has the format [{countrycode: "US", countryname: "United States"}, ...]
this.foundCountry = false;
@ -194,8 +219,9 @@
this.$countries.val(this.country);
this.$countries.attr("disabled", null).easyDropDown('enable');
context.JK.dropdown(this.$countries);
if(this.useEasyDropdown) {
context.JK.dropdown(this.$countries);
}
},
writeRegion: function (index, region) {
@ -220,7 +246,9 @@
this.$regions.val(userRegion)
this.$regions.attr("disabled", null).easyDropDown('enable');
context.JK.dropdown(this.$regions);
if(this.useEasyDropdown) {
context.JK.dropdown(this.$regions);
}
},
writeCity: function (index, city) {
@ -245,7 +273,9 @@
this.$cities.val(userCity)
this.$cities.attr("disabled", null).easyDropDown('enable');
context.JK.dropdown(this.$cities);
if(this.useEasyDropdown) {
context.JK.dropdown(this.$cities);
}
},
regionListFailure: function (jqXHR, textStatus, errorThrown) {

View File

@ -93,6 +93,7 @@
var claimedRecording = null;
var backing_track_path = null;
var jamTrack = null;
var metronomeMixer = null;
var playbackControls = null;
var promptLeave = false;
@ -115,6 +116,11 @@
var $metronomePlaybackSelect = null;
var $metronomePlaybackHelp = null;
var $templatePendingMetronome = null;
var $myTracks = null;
var $liveTracks = null;
var $audioTracks = null;
var $fluidTracks = null;
var mediaTrackGroups = [ChannelGroupIds.MediaTrackGroup, ChannelGroupIds.JamTrackGroup, ChannelGroupIds.MetronomeGroup];
var muteBothMasterAndPersonalGroups = [ChannelGroupIds.AudioInputMusicGroup, ChannelGroupIds.MediaTrackGroup, ChannelGroupIds.JamTrackGroup, ChannelGroupIds.MetronomeGroup];
@ -480,6 +486,12 @@
function checkMetronomeTransition() {
// trust backend over server
if(sessionModel.jamTracks() !== null || sessionModel.recordedJamTracks() !== null) {
// ignore all metronome events when jamtracks are open, because backend opens metronome mixer to play jamtrack tap-ins
logger.debug("ignore checkMetronomeTransition because JamTrack is open")
return;
}
var metronomeMasterMixers = getMetronomeMasterMixers();
if (metronomeMixer == null && metronomeMasterMixers.length > 0) {
@ -513,6 +525,7 @@
backing_track_path = currentSession == null ? null : currentSession.backing_track_path;
}
function checkRecordingTransition(currentSession) {
// handle claimed recordings
if (claimedRecording == null && (currentSession && currentSession.claimed_recording != null)) {
@ -604,6 +617,8 @@
$('.session-recordings .recording-controls').hide();
$closePlaybackRecording.show();
$('.session-recordings .session-recording-name').text('(No audio loaded)')
$('.session-recordings').attr('media-state', 'closed')
$('.session-livetracks').attr('media-state', 'closed')
}
}
function didSelfOpenMedia() {
@ -635,7 +650,7 @@
}
checkPendingMetronome();
resetOtherAudioContent();
resizeFluid();
/**
if ($('.session-recordings .track').length === 0) {
$('.session-recordings .when-empty').show();
@ -947,6 +962,11 @@
var recordedBackingTracks = sessionModel.recordedBackingTracks();
var backingTracks = sessionModel.backingTracks();
var recordedJamTracks = sessionModel.recordedJamTracks();
var jamTracks = sessionModel.jamTracks();
//logger.debug("localMediaMixers", localMediaMixers)
//logger.debug("peerMediaMixers", peerLocalMediaMixers)
// with mixer info, we use these to decide what kind of tracks are open in the backend
@ -981,29 +1001,57 @@
// additional check; if we can match an id in backing tracks or recorded backing track,
// we need to remove it from the recorded track set, but move it to the backing track set
var isBackingTrack = false
if(recordedBackingTracks) {
context._.each(recordedBackingTracks, function (recordedBackingTrack) {
if (mixer.id == 'L' + recordedBackingTrack.client_track_id) {
isBackingTrack = true;
return false; // break
}
})
}
if(backingTracks) {
context._.each(backingTracks, function (backingTrack) {
if (mixer.id == 'L' + backingTrack.client_track_id) {
isBackingTrack = true;
var isJamTrack = false;
if(jamTracks) {
// check if the ID matches that of an open jam track
context._.each(jamTracks, function (jamTrack) {
if (mixer.id == jamTrack.id) {
isJamTrack = true;
return false; // break
}
})
}
if(isBackingTrack) {
backingTrackMixers.push(mixer)
if(!isJamTrack && recordedJamTracks) {
// then check if the ID matches that of a open, recorded jam track
context._.each(recordedJamTracks, function (recordedJamTrack) {
if (mixer.id == recordedJamTrack.id) {
isJamTrack = true;
return false; // break
}
})
}
if(isJamTrack) {
jamTrackMixers.push(mixer)
}
else {
recordingTrackMixers.push(mixer);
var isBackingTrack = false
if (recordedBackingTracks) {
context._.each(recordedBackingTracks, function (recordedBackingTrack) {
if (mixer.id == 'L' + recordedBackingTrack.client_track_id) {
isBackingTrack = true;
return false; // break
}
})
}
if (backingTracks) {
context._.each(backingTracks, function (backingTrack) {
if (mixer.id == 'L' + backingTrack.client_track_id) {
isBackingTrack = true;
return false; // break
}
})
}
if (isBackingTrack) {
backingTrackMixers.push(mixer)
}
else {
// couldn't resolve this as a JamTrack or Backing track, must be a normal recorded file
recordingTrackMixers.push(mixer);
}
}
} else if(mediaType == 'PeerMediaTrack' || mediaType == 'BackingTrack') {
// BackingTrack
@ -1033,7 +1081,7 @@
if(jamTrackMixers.length > 0) {
renderJamTracks(jamTrackMixers);
}
if(metronomeTrackMixers.length > 0) {
if(metronomeTrackMixers.length > 0 && sessionModel.jamTracks() === null && sessionModel.recordedJamTracks() == null) {
renderMetronomeTracks(metronomeTrackMixers);
}
if(adhocTrackMixers.length > 0) {
@ -1170,7 +1218,20 @@
function renderJamTracks(jamTrackMixers) {
logger.debug("rendering jam tracks")
var jamTracks = sessionModel.jamTracks();
var jamTracks = []
var jamTrackName = 'JamTrack';
if(sessionModel.isPlayingRecording()) {
// only return managed mixers for recorded backing tracks
jamTracks = sessionModel.recordedJamTracks();
jamTrackName = sessionModel.recordedJamTrackName();
}
else {
// only return un-managed (ad-hoc) mixers for normal backing tracks
jamTracks = sessionModel.jamTracks();
jamTrackName = sessionModel.jamTrackName();
}
// pluck the 1st mixer, and assume that all other mixers in this group are of the same type (between JamTrack vs Peer)
@ -1179,7 +1240,7 @@
// using the server's info in conjuction with the client's, draw the recording tracks
if(jamTracks) {
$('.session-recording-name').text(sessionModel.getCurrentSession().jam_track.name);
$('.session-recording-name').text(jamTrackName);
var noCorrespondingTracks = false;
$.each(jamTrackMixers, function(index, mixer) {
@ -1870,6 +1931,39 @@
function otherAudioFilled() {
$('.session-recordings .when-empty').hide();
$('.session-recording-name-wrapper').show();
$('.session-recordings').attr('media-state', 'open');
$('.session-livetracks').attr('media-state', 'open');
}
function resizeFluid() {
var trackWidth = 78; // 70 width + 8 margin
var trackPadding = 30; // 15px left and right
var numLiveTracks = $liveTracks.find('.track').length;
var numAudioTracks = $audioTracks.find('.track').length;
var totalWidth = $fluidTracks.width();
// calculate desired audio tracks width
var minimumLiveTrackWidth = numLiveTracks * trackWidth + trackPadding;
var otherAudioWidth = numAudioTracks * trackWidth + trackPadding;
var liveTrackWidth = totalWidth - otherAudioWidth;
// live tracks get precedence over audio tracks, if there is a content over width usage
if(liveTrackWidth < minimumLiveTrackWidth) {
logger.debug("live track width trumping mode")
liveTrackWidth = minimumLiveTrackWidth;
otherAudioWidth = totalWidth - liveTrackWidth;
}
var otherAudioWidthPct = Math.floor(100 * otherAudioWidth/totalWidth);
var liveTrackWidthPct = Math.ceil(100 * liveTrackWidth/totalWidth);
logger.debug("resizeFluid: ", minimumLiveTrackWidth, otherAudioWidth, otherAudioWidthPct, liveTrackWidthPct, liveTrackWidthPct)
$audioTracks.css('width', otherAudioWidthPct + '%');
$liveTracks.css('width', liveTrackWidthPct + '%');
}
function _addRecordingTrack(trackData, mixer, oppositeMixer) {
@ -2169,6 +2263,8 @@
}
}
$.each(mixerIds, function(i,v) {
var mixerId = v;
// behavior: if this is the user's track in personal mode, then we mute the track globally
@ -2553,6 +2649,8 @@
{ title: "JamTrack Can Not Open",
text: "Unable to open your JamTrack. Please contact support@jamkazam.com"
}, null, true);
} else {
rest.playJamTrack(jamTrack.id);
}
}
})
@ -2806,20 +2904,32 @@
}
function onChangePlayPosition(e, data){
logger.debug("calling jamClient.SessionTrackSeekMs(" + data.positionMs + ")");
var seek = data.positionMs;
if(data.playbackMonitorMode == context.JK.PLAYBACK_MONITOR_MODE.JAMTRACK) {
context.jamClient.SessionJamTrackSeekMs(data.positionMs);
// if positionMs == 0, then seek it back to whatever the earliest play start is to catch all the prelude
if(seek == 0) {
var duration = context.jamClient.SessionGetJamTracksPlayDurationMs();
seek = duration.start;
}
}
logger.debug("calling jamClient.SessionTrackSeekMs(" + seek + ")");
if(data.playbackMonitorMode == context.JK.PLAYBACK_MONITOR_MODE.JAMTRACK) {
context.jamClient.SessionJamTrackSeekMs(seek);
}
else {
context.jamClient.SessionTrackSeekMs(data.positionMs);
context.jamClient.SessionTrackSeekMs(seek);
}
}
function startStopRecording() {
// check first if a jamtrack is loaded, and playing; if so, tell user to stop the play
if(sessionModel.jamTracks() && context.jamClient.isSessionTrackPlaying()) {
/**if(sessionModel.jamTracks() && context.jamClient.isSessionTrackPlaying()) {
app.notify(
{ title: "Can't Recording a Play JamTrack",
text: "Stop the JamTrack before trying to recording." },
@ -2827,7 +2937,7 @@
true);
return;
}
}*/
if(sessionModel.recordingModel.isRecording()) {
sessionModel.recordingModel.stopRecording();
@ -2938,6 +3048,9 @@
$(document).on("change", ".metronome-select", onMetronomeChanged)
$metronomePlaybackSelect.metronomePlaybackMode().on(EVENTS.METRONOME_PLAYBACK_MODE_SELECTED, metronomePlaybackModeChanged)
context.JK.helpBubble($metronomePlaybackHelp, 'metromone-playback-modes', {} , {offsetParent: $screen, width:'400px'});
$(document).on('layout_resized', function() {
resizeFluid();
});
}
this.initialize = function(localRecordingsDialogInstance, recordingFinishedDialogInstance, friendSelectorDialog) {
@ -2971,6 +3084,10 @@
$metronomePlaybackSelect = $('#metronome-playback-select')
$metronomePlaybackHelp = $('#metronome-playback-help')
$templatePendingMetronome = $('#template-pending-metronome');
$myTracks = $screen.find('.session-mytracks');
$liveTracks = $screen.find('.session-livetracks');
$audioTracks = $screen.find('.session-recordings');
$fluidTracks = $screen.find('.session-fluidtracks')
events();

View File

@ -127,6 +127,32 @@
}
}
function recordedJamTracks() {
if(currentSession && currentSession.claimed_recording) {
return currentSession.claimed_recording.recording.recorded_jam_track_tracks
}
else {
return null;
}
}
function jamTrackName() {
if (currentSession && currentSession.jam_track) {
return currentSession.jam_track.name;
}
else {
return null;
}
}
function recordedJamTrackName() {
if(currentSession && currentSession.claimed_recording && currentSession.claimed_recording.recording.jam_track) {
return currentSession.claimed_recording.recording.jam_track.name;
}
else {
return null;
}
}
// did I open up the current JamTrack?
function selfOpenedJamTracks() {
return currentSession && (currentSession.jam_track_initiator_id == context.JK.currentUserId)
@ -843,8 +869,11 @@
this.backingTrack = backingTrack;
this.backingTracks = backingTracks;
this.recordedBackingTracks = recordedBackingTracks;
this.recordedJamTracks = recordedJamTracks;
this.setUserTracks = setUserTracks;
this.recordedTracks = recordedTracks;
this.jamTrackName = jamTrackName;
this.recordedJamTrackName = recordedJamTrackName;
this.jamTracks = jamTracks;
this.participants = participants;
this.joinSession = joinSession;

View File

@ -30,10 +30,17 @@
e.preventDefault();
if (!context.JK.currentUserId) {
window.location = '/client#/signin';
window.location = '/client#/checkoutSignin';
}
else {
window.location = '/client#/order';
app.user().done(function(user) {
if(user.has_recurly_account && user.reuse_card) {
window.location = '/client#/checkoutOrder';
}
else {
window.location = '/client#/checkoutPayment';
}
})
}
}

View File

@ -13,6 +13,7 @@
var notificationPanel = null;
var chatPanel = null;
var me = null;
var $sidebar = null;
function initializeSearchPanel() {
$('#search_text_type').change(function() {
@ -39,7 +40,9 @@
function initializeFriendsPanel() {
$('#sidebar-search-header').hide();
refreshFriends();
app.user().done(function() {
refreshFriends();
})
return false;
}
@ -406,11 +409,24 @@
me = this;
invitationDialog = invitationDialogInstance;
textMessageDialog = textMessageDialogInstance;
events();
initializeSearchPanel();
initializeFriendsPanel();
initializeChatPanel();
initializeNotificationsPanel();
$sidebar = $('#sidebar-div')
app.user()
.done(function() {
events();
initializeSearchPanel();
initializeFriendsPanel();
initializeChatPanel();
initializeNotificationsPanel();
})
.fail(function(arg1) {
if(arg1 == "not_logged_in") {
$('#search-input').attr('disabled', 'disabled')
$('.sidebar .invite-friend-row').click(function() {
app.layout.showDialog('login-required-dialog')
});
$sidebar.addClass('not-logged-in')
}
})
};
this.refreshFriends = refreshFriends;

View File

@ -4,7 +4,7 @@ context.JK ||= {};
context.JK.SiteValidator = class SiteValidator
constructor: (site_type) ->
constructor: (site_type, success_callback, fail_callback) ->
@EVENTS = context.JK.EVENTS
@rest = context.JK.Rest()
@site_type = site_type
@ -18,6 +18,8 @@ context.JK.SiteValidator = class SiteValidator
@is_rec_src = false
@deferred_status_check = null
@is_validating = false
@success_callback = success_callback
@fail_callback = fail_callback
init: () =>
this.renderErrors({})
@ -53,6 +55,8 @@ context.JK.SiteValidator = class SiteValidator
validateSite: () =>
unless data = this.dataToValidate()
if @success_callback
@success_callback(@input_div)
return null
this.setSiteStatus(null)
@spinner.show()
@ -68,11 +72,16 @@ context.JK.SiteValidator = class SiteValidator
this.renderErrors({})
if @deferred_status_check
@deferred_status_check.resolve()
if @success_callback
@success_callback(@input_div)
else
this.setSiteStatus(false)
this.renderErrors(response)
if @deferred_status_check
@deferred_status_check.reject()
if @fail_callback
@fail_callback(@input_div)
@deferred_status_check = null
@logger.debug("site_status = "+@site_status)
@ -120,8 +129,8 @@ context.JK.SiteValidator = class SiteValidator
context.JK.RecordingSourceValidator = class RecordingSourceValidator extends SiteValidator
constructor: (site_type) ->
super(site_type)
constructor: (site_type, success_callback, fail_callback) ->
super(site_type, success_callback, fail_callback)
@recording_sources = []
@is_rec_src = true
@add_btn = @input_div.find('a.add-recording-source')

View File

@ -981,6 +981,19 @@
})
}
context.JK.flash = function(msg, options) {
options = options || {}
var $flash = $(context._.template($('#template-flash-notice').html(), {}, { variable: 'data' }));
$flash.find('.flash-content').html(msg);
$('body').prepend($flash)
if(options.hide) {
// slide down (take 1 sec to do it), sit for 5, then leave over 1 second
setTimeout(function() {$flash.slideUp(1000, 'swing') }, options.hide * 1000)
}
}
context.JK.hasFlash = function () {
var hasFlash = false;

View File

@ -13,7 +13,7 @@
var platformName; // mac, windows, linux
var platformDisplay; // Mac, Windows, Linux
var platform = selectedPlatform; //MacOSX, Win32, Unix
var platformName1, platformName2, platform1, platform2;
var platformName1, platformName2, platform1, platform2, platformDisplay1, platformDisplay2;
var uri = downloadUris[selectedPlatform];
// prepare template varaibles
@ -21,21 +21,27 @@
platformName = "linux";
platformDisplay = "Linux"
platformName1 = "mac";
platformDisplay1 = "Mac";
platformName2 = "windows";
platformDisplay2 = "Windows";
platform1 = "MacOSX";
platform2 = "Win32"
} else if(selectedPlatform == "Win32") {
platformName = "windows";
platformDisplay = "Windows";
platformName1 = "mac";
platformDisplay1 = "Mac";
platformName2 = "linux"
platformDisplay2 = "Linux";
platform1 = "MacOSX";
platform2 = "Unix";
} else if(selectedPlatform == "MacOSX") {
platformName = "mac";
platformDisplay = "Mac";
platformName1 = "windows";
platformDisplay1 = "Windows";
platformName2 = "linux";
platformDisplay2 = "Linux";
platform1 = "Win32";
platform2 = "Unix";
}
@ -48,7 +54,9 @@
platformName : platformName,
platformDisplay : platformDisplay,
platformName1 : platformName1,
platformDisplay1 : platformDisplay1,
platformName2 : platformName2,
platformDisplay2 : platformDisplay2,
platform1: platform1,
platform2: platform2,
uri : uri ? uri : '#',
@ -105,9 +113,20 @@
function removeSpinner() {
$('body.web .spinner-large').remove();
}
function flashCongratulations() {
context.JK.flash('Congratulations!<br>Your account is ready.', {hide:10})
}
function listClients(congratulations) {
isCongratulations = congratulations;
if(isCongratulations) {
flashCongratulations();
}
else {
//flashCongratulations();
}
var rest = context.JK.Rest();
var currentOS = context.JK.detectOS();
var downloads = $('.downloads');

View File

@ -458,8 +458,8 @@
}
/** account sessions */
.account-sessions {
div#account-scheduled-sessions {
.account-sessions, .account-jamtracks {
div#account-scheduled-sessions, #account-my-jamtracks {
position: relative;
display: block;
overflow: auto;

View File

@ -25,6 +25,10 @@
.sample {
margin: 3px 5px 10px 0px;
a.add-recording-source {
margin-top: 2px !important;
}
}
.sample-list {

View File

@ -1,25 +1,24 @@
@import "client/common.css.scss";
.checkout-navigation {
padding: 20px 0px;
.nav-signin, .nav-payment-info, .nav-place-order {
width: 30%;
margin-right:50px;
float: left;
}
.nav-signin {
margin-left: 5%;
}
.nav-place-order {
margin-right: 5%;
margin-left: 30px;
}
.nav-text {
font-size: 17px;
float: left;
color:$ColorTextDisabled;
}
.nav-text.selected {
font-weight: bold;
color:$ColorTextHighlight;
}
.nav-arrow {
@ -28,144 +27,6 @@
}
}
.checkout-signin, .checkout-payment-info, .checkout-place-order {
padding: 30px;
.signin-form {
padding: 10px;
strong {
font-weight: bold;
}
label {
display: inline;
}
.signin-password {
margin-left: 33px;
}
.login-error {
background-color: #330000;
border: 1px solid #990000;
padding:4px;
div.actions {
margin-top:10px;
}
}
.login-error-msg {
display:none;
margin-top:10px;
text-align:center;
color:#F00;
font-size:11px;
}
.login-error .login-error-msg {
display:block;
}
}
form.payment-info {
width: 100%;
input[type="text"] {
width: 90%;
}
.billing-address {
float: left;
width: 50%;
h2.billing-caption {
margin: 20px 5px;
font-size: 16px;
}
.billing-label {
padding-top: 8px;
width: 30%;
float: left;
text-align: right;
margin-right: 5px;
}
.billing-value {
width: 65%;
text-align: left;
float: left;
}
}
.payment-method {
float: left;
width: 50%;
h2.payment-method-caption {
margin: 20px 5px;
font-size: 16px;
}
.card-label {
padding-top: 8px;
width: 35%;
float: left;
text-align: right;
margin-right: 5px;
}
.card-value {
width: 60%;
text-align: left;
float: left;
}
.save-card-checkbox {
float:left;
display:block;
margin-right:5px;
}
}
.shipping-address {
float: left;
width: 50%;
h2.shipping-address-label {
margin: 20px 5px;
font-size: 16px;
}
.shipping-as-billing {
float:left;
display:block;
margin-right:5px;
}
.divBillingHelper {
padding-top: 2px;
}
.shipping-label {
padding-top: 8px;
width: 30%;
float: left;
text-align: right;
margin-right: 5px;
}
.shipping-value {
width: 65%;
text-align: left;
float: left;
}
}
}
}
.thanks-panel {
padding: 30px;
@ -203,88 +64,3 @@
}
}
}
.order-panel {
padding: 30px;
.order-header {
h2 {
font-size: 16px;
}
}
.order-content {
margin-top: 20px;
}
.order-left-page {
float: left;
width: 60%;
.payment-info-page {
padding: 5px;
.info-caption-link {
.caption-text {
float: left;
}
.caption-link {
float: left;
margin-left: 5px;
}
}
.address-info {
width: 50%;
float: left;
}
.payment-method-info {
width: 50%;
float: left;
}
}
.order-items-page {
padding: 5px;
.cart-item-caption {
width: 50%;
text-align: left;
float: left;
}
.cart-item-caption#header {
font-weight: bold;
}
.cart-item-price {
width: 25%;
text-align: right;
float: left;
}
.cart-item-quantity {
width: 25%;
text-align: right;
float: left;
}
.cart-items {
margin-top: 10px;
}
.cart-item {
margin-top: 10px;
}
}
}
.order-right-page {
float: right;
width: 35%;
text-align: center;
.order-total {
color: #ed3618;
}
}
}

View File

@ -0,0 +1,204 @@
@import "client/common.css.scss";
#checkoutOrderScreen {
p {
font-size:12px;
margin:0;
}
.order-prompt {
color:white;
line-height:125%;
}
h2 {
color:white;
background-color:#4d4d4d;
font-weight:normal;
margin: 0 0 10px 0;
font-size:14px;
padding: 3px 0 3px 10px;
height: 14px;
line-height: 14px;
vertical-align: middle;
text-align:left;
}
.action-bar {
margin-top:20px;
}
.line {
margin:10px 0 10px;
border-width:0 0 1px 0;
border-color:#ccc;
border-style:solid;
}
#checkout-info-help {
margin-right:1px;
}
.billing-info-item {
margin-bottom:3px;
}
.country {
margin-left:15px;
}
.billing-address {
margin-bottom:20px;
}
.order-panel {
padding: 30px;
min-width:730px;
.place-order {
font-size: 14px;
padding: 1px 3px;
line-height: 15px;
}
.place-order-center {
text-align:center;
margin:20px 0 20px;
}
.change-payment-info {
position:absolute;
font-size:12px;
left:180px;
}
.billing-caption {
margin-bottom:5px;
float:left;
position:relative;
}
.order-header {
h2 {
font-size: 16px;
}
}
.shipping-address {
display:none;
}
.order-help {
margin:20px 0 30px;
}
.order-summary {
padding:0 20px;
.billing-caption {
float:none;
margin-bottom:10px;
}
}
.order-items-header {
float:left;
margin-bottom:5px;
}
.order-items-value {
float:right;
}
.order-content {
margin-top: 20px;
background-color:#262626;
}
.order-left-page {
float: left;
width: 65%;
background-color:#262626;
border-width:0 1px 0 0;
border-style:solid;
border-color:#333;
@include border_box_sizing;
.payment-info-page {
.info-caption-link {
.caption-text {
float: left;
}
.caption-link {
float: left;
margin-left: 5px;
}
}
.address-info {
width: 50%;
float: left;
padding:0 10px;
@include border_box_sizing;
margin-bottom:30px;
}
.payment-method-info {
width: 50%;
float: left;
padding:0 10px;
@include border_box_sizing;
}
}
.order-items-page {
.cart-item-caption {
width: 50%;
text-align: left;
float: left;
margin-bottom:10px;
@include border_box_sizing;
}
.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 {
}
}
}
.order-right-page {
float: left;
width: 35%;
text-align: left;
background-color:#262626;
@include border_box_sizing;
.grand-total {
color: #ed3618;
}
}
}
}

View File

@ -0,0 +1,239 @@
@import "client/common.css.scss";
#checkoutPaymentScreen {
.payment-wrapper {
padding:10px 30px;
min-width:600px;
}
p {
font-size:12px;
margin:0;
}
.payment-prompt {
color:white;
line-height:125%;
}
.field.error {
background-color: transparent !important;
padding: 0 !important;
border-width:0 !important;
li {
list-style:none;
}
}
h2 {
color:white;
background-color:#4d4d4d;
font-weight:normal;
margin: 0 0 30px 0;
font-size:14px;
padding-left:10px;
&.shipping-address-label {
//margin-top:10px;
}
}
.field {
margin-bottom:5px;
}
#payment_error {
@include border_box_sizing;
background-color: #300;
padding: 5px;
border: solid 1px #900;
}
form.payment-info {
width: 100%;
display:block;
background-color:#262626;
padding-bottom: 10px;
margin-bottom: 20px;
input[type="text"], input[type="password"] {
width: 90%;
@include border_box_sizing;
}
select#billing-country {
width:90%;
@include border_box_sizing;
}
&.signed-in {
.row.second {
.left-side {
width:100%;
}
.right-side {
display:none;
}
}
}
&.not-signed-in {
.row.second {
.left-side {
display:none;
}
.right-side {
width:100%;
}
}
}
#divShippingFirstName, #divShippingLastName {
display:none;
}
.row {
margin-top:20px;
width:100%;
clear:both;
}
.left-side, .right-side {
float: left;
width: 50%;
@include border_box_sizing;
}
.left-side {
border-width:0 1px 0 0;
border-style:solid;
border-color:#333;
}
.jamkazam-account-signup {
.account-label {
padding-top: 4px;
width: 35%;
float: left;
text-align: left;
padding-left: 5px;
@include border_box_sizing;
}
.account-value {
width: 65%;
text-align: left;
float: left;
@include border_box_sizing;
}
div.terms-of-service.ichecbuttons {
margin-left:5px;
.icheckbox_minimal {
float: left;
display: block;
margin: 5px 5px 0 0;
}
}
.terms-of-service-label-holder {
font-size:12px;
line-height:18px;
top:4px;
position:relative;
float:left;
}
}
.hint {
font-size:12px;
}
.billing-address {
.billing-label {
padding-top: 4px;
width: 35%;
float: left;
text-align: left;
padding-left: 5px;
@include border_box_sizing;
}
.billing-value {
width: 65%;
text-align: left;
float: left;
@include border_box_sizing;
}
}
.payment-method {
.card-label {
padding-top: 4px;
width: 35%;
float: left;
text-align: left;
padding-left: 5px;
@include border_box_sizing;
position:relative;
}
.card-value {
width: 65%;
text-align: left;
float: left;
@include border_box_sizing;
}
.save-card-checkbox, .reuse-existing-card-checkbox {
float:left;
display:block;
margin-right:5px;
}
label[for="reuse-existing-card"], label[for="save-card"] {
line-height: 18px;
vertical-align: middle;
}
.reuse-existing-card-helper {
margin-bottom:10px;
}
}
.shipping-address {
.shipping-as-billing {
float:left;
display:block;
margin: 0 5px 10px;
}
.divBillingHelper {
padding-top: 2px;
label {
}
}
.shipping-label {
padding-top: 4px;
width: 35%;
float: left;
text-align: left;
padding-left: 5px;
@include border_box_sizing;
}
.shipping-value {
width: 65%;
text-align: left;
float: left;
@include border_box_sizing;
}
}
}
}

View File

@ -0,0 +1,167 @@
@import "client/common.css.scss";
#checkoutSignInScreen {
.content-holder {
margin-top:40px;
color:$ColorTextTypical;
&.signed-in {
.left-side, .right-side {
display:none;
}
}
&.not-signed-in {
.already-signed-in {
display:none;
}
}
}
.already-signed-in {
text-align:center;
}
a.forgot-password {
position:absolute;
top:0;
left:0;
font-size:10px;
}
.signin-submit {
margin-right:2px;
}
.signup-later-prompt {
margin:30px 0 30px 0;
text-align:center;
}
.signin-prompt {
float:right;
text-align:left;
margin:30px 0 15px 0;
width:60%;
max-width:300px;
@include border_box_sizing;
span {
text-align:left;
}
}
p {
font-size:14px;
line-height:125%;
}
label {
display:inline-block;
width:80px;
text-align:left;
}
.login-error {
div.actions {
margin-top:10px;
}
.field {
background-color: #330000;
border: 1px solid #990000;
padding:8px;
}
}
.field {
display:inline;
@include border_box_sizing;
}
.login-error-msg {
display:none;
margin-top:10px;
text-align:center;
color:#F00;
font-size:11px;
width:60%;
max-width:300px;
@include border_box_sizing;
float:right;
margin-bottom:10px;
}
.login-error .login-error-msg {
display:block;
}
h3
{
color:$ColorTextHighlight;
text-align:center;
}
input {
width:60%;
max-width:300px;
@include border_box_sizing;
}
input[name="email"] {
margin-bottom:13px;
}
.btnNext {
margin:0 auto;
}
.left-side {
float:left;
width:50%;
@include border_box_sizing;
border-width:0 1px 0 0;
border-color:white;
border-style:solid;
text-align:right;
padding: 0 50px;
margin-bottom:20px;
.actions {
width:60%;
max-width:300px;
@include border_box_sizing;
position:relative;
float:right;
}
}
.left-side-content {
@include border_box_sizing;
width:100%;
}
.right-side {
float:left;
width:50%;
@include border_box_sizing;
padding: 0 50px;
.actions {
width:100%;
text-align:center;
}
}
.facebook-prompt {
margin:40px 0 10px 0;
float:right;
text-align:left;
width:249px;
@include border_box_sizing;
}
}

View File

@ -56,6 +56,10 @@
*= require ./jamtrack
*= require ./shoppingCart
*= require ./checkout
*= require ./checkout_signin
*= require ./checkout_payment
*= require ./checkout_order
*= require ./genreSelector
*= require ./sessionList
*= require ./searchResults
*= require ./clientUpdate

View File

@ -12,8 +12,13 @@ $ColorLinkHover: #82AEAF;
$ColorSidebarText: #a0b9bd;
$ColorScreenBackground: lighten($ColorUIBackground, 10%);
$ColorTextBoxBackground: #c5c5c5;
$ColorTextBoxDisabledBackground: #999;
$ColorRecordingBackground: #471f18;
$ColorTextHighlight: white;
$ColorTextTypical: #CCCCCC;
$ColorTextDisabled: #AAAAAA;
$color1: #006AB6; /* mid blue */
$color2: #9A9084; /* warm gray */
$color3: #B11254; /* magenta */
@ -292,4 +297,24 @@ $fair: #cc9900;
}
}
.badge-number {
font-size:25px;
color:white;
background-color:$ColorScreenPrimary;
width:30px;
height:30px;
-webkit-border-radius:50%;
-moz-border-radius:50%;
border-radius:50%;
text-align:center;
display:inline-block;
border:2px solid white;
margin-right:10px;
&.disabled {
color:$ColorTextDisabled;
border-color:$ColorTextDisabled;
background-color:transparent;
}
}

View File

@ -222,6 +222,10 @@
border:none;
-webkit-box-shadow: inset 2px 2px 3px 0px #888;
box-shadow: inset 2px 2px 3px 0px #888;
&:disabled {
background-color: $ColorTextBoxDisabledBackground;
}
}
}
@ -383,6 +387,10 @@ a.arrow-down {
select {
font-size:11px;
margin-top:4px;
}
.dropdown-wrapper {
margin-right: 4px;
}
}

View File

@ -0,0 +1,24 @@
body > .flash-notice {
background-color:#006673;
position:absolute;
width:300px;
height:100px;
line-height: 100px;
margin:0 auto;
border-width:0 1px 1px;
border-style:solid;
border-color:white;
text-align:center;
left:50%;
margin-left:-150px;
.flash-content {
color:white;
font-size:20px;
display: inline-block;
vertical-align: middle;
line-height: normal;
}
}

View File

@ -8,6 +8,10 @@
background-repeat: no-repeat;
background-position: bottom left;
border: 1px solid $translucent1;
&.not-logged-in {
opacity:0.6;
}
}
.homecard.createsession {
background-image: url(/assets/content/bkg_home_create.jpg);
@ -38,7 +42,7 @@
background-image: url(/assets/content/bkg_home_bands.jpg);
}
.homecard.musicians {
background-image: url(/assets/content/bkg_home_musicians.jpg);
background-image: url(/assets/content/bkg_home_guitar.jpg);
}
.homecard.jamtrack {
background-image: url(/assets/content/bkg_home_jamtracks.jpg);
@ -94,7 +98,7 @@
background-image: url(/assets/content/bkg_home_bands_x.jpg);
}
.homecard.musicians.hover {
background-image: url(/assets/content/bkg_home_musicians_x.jpg);
background-image: url(/assets/content/bkg_home_guitar_x.jpg);
}
.homecard.jamtrack.hover {
background-image: url(/assets/content/bkg_home_jamtracks_x.jpg);

View File

@ -322,6 +322,10 @@ input[type="text"], input[type="password"]{
border:none;
padding:3px;
font-size:15px;
&:disabled {
background-color: $ColorTextBoxDisabledBackground;
}
}
textarea {
@ -388,6 +392,24 @@ textarea {
padding:5px;
border: solid 1px #900;
.error-text {
display:block;
font-size:11px;
color:#F00;
margin:10px 0 0;
}
&.transparent {
background-color:transparent;
padding:0;
border-width:0px;
.error-text {
margin:5px 0 0;
font-size:14px;
font-weight:bold;
}
}
}
.error input {
@ -399,12 +421,6 @@ textarea {
display:none;
}
.error .error-text {
display:block;
font-size:11px;
color:#F00;
margin:10px 0 0;
}
.grey {
color:#999;
}

View File

@ -1,4 +1,74 @@
@import 'common';
#jamtrackLanding {
ul {
li {
margin: 1px 4px 1px 4em;
font-size:9px;
}
}
.list-columns {
h2 {
font-size: 16pt;
font-weight:300;
font-style: bolder;
font-family: verdana;
text-transform: lowercase;
margin-bottom: 2em;
}
.free-jamtrack {
font-size: 11pt;
padding: 3px;
@include border-radius(7px);
background-color:$ColorScreenPrimary;
text-align: center;
vertical-align: center;
}
.what, .howto {
margin-bottom: 2em;
}
p {
font-size: 12pt !important;
font-weight: normal;
line-height: 16px;
color: #dddddd;
* {
font-size: 10pt !important;
font-weight: normal;
line-height: 16px;
}
}
.about {
@include border_box_sizing;
float: left;
width: 50%;
> * {
margin: 4px;
}
}
.browse {
@include border_box_sizing;
float: left;
width: 50%;
> * {
margin: 4px;
}
}
}
}
#jamtrackScreen {
.jamtrack-header {
background-color: #4c4c4c;
font-weight: bold;
text-transform: uppercase;
height: 2em;
padding: 4px;
}
a.jamtrack_help {
color: #fff;
text-decoration: none;
@ -11,6 +81,8 @@
.jamtrack-content {
text-align: center;
border: 1px solid #222222;
padding: 2px
}
.no-jamtracks-msg {
@ -23,14 +95,16 @@
}
.jamtrack-detail {
@include border_box_sizing;
float: left;
width: 50%;
width: 30%;
padding: 10px 0px;
.detail-label {
width: 40%;
float: left;
margin-top: 5px;
font-weight: 400;
font-size: 11pt;
}
.detail-value {
@ -56,15 +130,17 @@
.jamtrack-detail-btn {
cursor: pointer;
margin-top: 5px;
margin-top: 7px;
margin-right: 5px;
padding-top: 5px;
vertical-align: bottom;
}
}
.jamtrack-tracks {
@include border_box_sizing;
float: left;
width: 25%;
width: 50%;
padding: 10px 0px;
.tracks-caption {
@ -78,6 +154,7 @@
.instrument-image {
float: left;
margin-right: 2px;
}
.instrument-desc {
@ -88,8 +165,9 @@
}
.jamtrack-action {
@include border_box_sizing;
float: left;
width: 25%;
width: 20%;
padding: 10px 0px;
text-align: center;
@ -113,4 +191,30 @@
width: 60%;
}
}
}
#jamtrack-license-dialog {
.dialog-inner {
height: auto;
.content-body {
max-height: auto;
.content-body-scroller {
height: 350px;
.paragraph {
margin-bottom: 1em;
}
overflow: hidden;
}
border: 1px solid #222;
margin: 4px 4px 8px 4px;
}
}
}
.jamtrack_buttons {
margin: 8px 4px 12px 4px;
}
.capitalize {
text-transform: capitalize
}

View File

@ -13,6 +13,7 @@
background-color:#4c4c4c;
min-height:20px;
position:relative;
min-width:690px;
}
.pending-metronome {
@ -55,7 +56,11 @@
.recording-position {
display:inline-block;
width:80%;
width:calc(100% - 27px - 47px); // 27 accounts for play arrow, 47px acconts for the total time (0:00)
position:absolute;
left:0;
margin-left:27px;
font-family:Arial, Helvetica, sans-serif;
font-size:11px;
@ -150,6 +155,7 @@
.session-add {
margin-top:9px;
margin-right:10px;
}
.session-add a {
vertical-align:top;
@ -193,6 +199,139 @@
color:#ffcc00;
cursor:help;
}
.leave {
margin-right:25px;
}
$mytracks-minwidth: 180px;
$livetracks-minwidth:180px;
$otheraudio-minwidth:195px;
$otheraudio-open-minwidth:230px;
.session-mytracks {
padding-left:15px;
float:left;
display:inline-block;
width:$mytracks-minwidth;
border-right:solid 1px #666;
@include border_box_sizing;
}
.session-fluidtracks {
width:calc(100% - #{$mytracks-minwidth});
position:relative;
float:left;
@include border_box_sizing;
}
.session-livetracks {
padding-left:15px;
padding-right:15px;
float:left;
display:inline-block;
min-width:$livetracks-minwidth;
max-width:calc(100% - #{$otheraudio-minwidth});
width:calc(100% - #{$otheraudio-minwidth});
border-right:solid 1px #666;
@include border_box_sizing;
&[media-state="open"] {
width:calc(100% - #{$otheraudio-open-minwidth});
max-width:calc(100% - #{$otheraudio-open-minwidth});
min-width:#{$livetracks-minwidth};
}
.recording {
left: 50%;
margin-left: -67px;
}
.recording-controls {
min-width:230px;
}
}
.session-recordings {
padding-left:15px;
padding-right:15px;
display:inline-block;
max-width:calc(100% - #{$livetracks-minwidth});
min-width:#{$otheraudio-minwidth};
width:#{$otheraudio-minwidth};
float:left;
@include border_box_sizing;
&[media-state="open"] {
width:$otheraudio-open-minwidth;
min-width:$otheraudio-open-minwidth;
max-width:calc(100% - #{$livetracks-minwidth});
}
.recording.metronome-mode {
margin-left:-100px;
}
}
#tracks .when-empty.livetracks {
margin: 0px;
padding:0px;
display:block;
padding-top: 125px;
vertical-align:middle;
text-align:center;
}
#tracks .when-empty.recordings {
//padding-top: 137px;
text-align:left;
padding-top:6px;
margin:0;
}
#tracks .when-empty a {
text-decoration: underline;
color: inherit;
}
.session-add {
margin-top:9px;
margin-bottom:8px;
font-size:16px;
height: 22px;
min-height: 22px;
max-height: 22px;
}
.session-add a {
color:#ccc;
text-decoration: none;
}
.session-add a img {
vertical-align:bottom;
}
.session-tracks-scroller {
position:relative;
overflow-x:auto;
overflow-y:hidden;
width:100%;
height:370px;
float:left;
white-space:nowrap;
}
.play-controls-holder {
width:100%;
text-align:center;
.recording {
left:50%;
margin-left:-46.5%;
}
}
}
@ -334,84 +473,6 @@ table.vu td {
margin-left:15px;
}
.leave {
margin-right:25px;
}
.session-mytracks {
margin-left:15px;
float:left;
display:inline-block;
width:19%;
min-width:165px;
border-right:solid 1px #666;
}
.session-livetracks {
margin-left:15px;
float:left;
display:inline-block;
width:35%;
min-width:220px;
border-right:solid 1px #666;
}
.session-recordings {
margin-left:15px;
display:inline-block;
width:35%;
min-width:220px;
}
#tracks .when-empty.livetracks {
margin: 0px;
padding:0px;
display:block;
padding-top: 125px;
vertical-align:middle;
text-align:center;
}
#tracks .when-empty.recordings {
//padding-top: 137px;
text-align:left;
padding-top:6px;
margin:0;
}
#tracks .when-empty a {
text-decoration: underline;
color: inherit;
}
.session-add {
margin-top:9px;
margin-bottom:8px;
font-size:16px;
height: 22px;
min-height: 22px;
max-height: 22px;
}
.session-add a {
color:#ccc;
text-decoration: none;
}
.session-add a img {
vertical-align:bottom;
}
.session-tracks-scroller {
position:relative;
overflow-x:auto;
overflow-y:hidden;
width:100%;
height:370px;
float:left;
white-space:nowrap;
}
@ -750,7 +811,6 @@ table.vu td {
margin-top:15px;
padding:3px;
height:19px;
width:95%;
background-color:#242323;
position:absolute;
text-align:center;

View File

@ -1,7 +1,7 @@
@import "client/common";
table.findsession-table, table.local-recordings, table.open-jam-tracks, table.open-backing-tracks, #account-session-detail {
table.findsession-table, table.local-recordings, table.open-jam-tracks, table.open-backing-tracks, table.cart-items, #account-session-detail, table.payment-table {
.latency-unacceptable {
width: 50px;
@ -64,7 +64,7 @@ table.findsession-table, table.local-recordings, table.open-jam-tracks, table.op
text-align:center;
}
}
table.findsession-table, table.local-recordings, table.open-jam-tracks, table.open-backing-tracks {
table.findsession-table, table.local-recordings, table.open-jam-tracks, table.open-backing-tracks, table.cart-items, table.payment-table {
width:98%;
height:10%;
font-size:11px;

View File

@ -29,9 +29,7 @@
}
.cart-item-caption {
width: 50%;
text-align: left;
float: left;
text-align: left;
}
.cart-item-caption#header {
@ -39,21 +37,15 @@
}
.cart-item-price {
width: 15%;
text-align: right;
float: left;
text-align: right;
}
.cart-item-quantity {
width: 15%;
text-align: right;
float: left;
text-align: right;
}
.cart-item-actions {
width: 20%;
text-align: center;
float: left;
//text-align: center;
}
.cart-items {

View File

@ -5,6 +5,10 @@
background-color: $ColorElementPrimary;
&.not-logged-in {
opacity:0.6;
}
.panel-header {
margin:0px;
padding:0px;

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