diff --git a/admin/app/admin/jam_track_right.rb b/admin/app/admin/jam_track_right.rb index 5e7c3687b..3c591234a 100644 --- a/admin/app/admin/jam_track_right.rb +++ b/admin/app/admin/jam_track_right.rb @@ -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 diff --git a/admin/app/admin/jam_tracks.rb b/admin/app/admin/jam_tracks.rb index 96f341829..6c90018e6 100644 --- a/admin/app/admin/jam_tracks.rb +++ b/admin/app/admin/jam_tracks.rb @@ -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 diff --git a/admin/app/models/cohort.rb b/admin/app/models/cohort.rb index 29523a46d..2b72a3936 100644 --- a/admin/app/models/cohort.rb +++ b/admin/app/models/cohort.rb @@ -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) diff --git a/admin/app/views/admin/jam_tracks/_jam_track_right_fields.html.slim b/admin/app/views/admin/jam_tracks/_jam_track_right_fields.html.slim new file mode 100644 index 000000000..9bf130e56 --- /dev/null +++ b/admin/app/views/admin/jam_tracks/_jam_track_right_fields.html.slim @@ -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 diff --git a/admin/config/initializers/jam_track_tracks.rb b/admin/config/initializers/jam_track_tracks.rb index 36f82d79e..b6367cd58 100644 --- a/admin/config/initializers/jam_track_tracks.rb +++ b/admin/config/initializers/jam_track_tracks.rb @@ -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) diff --git a/admin/spec/factories.rb b/admin/spec/factories.rb index a1e906a48..d6888a6bc 100644 --- a/admin/spec/factories.rb +++ b/admin/spec/factories.rb @@ -11,6 +11,7 @@ FactoryGirl.define do state "NC" country "US" terms_of_service true + reuse_card true factory :admin do diff --git a/db/manifest b/db/manifest index 4a3c59b8b..444624782 100755 --- a/db/manifest +++ b/db/manifest @@ -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 diff --git a/db/up/drop_position_unique_jam_track.sql b/db/up/drop_position_unique_jam_track.sql new file mode 100644 index 000000000..6055702d4 --- /dev/null +++ b/db/up/drop_position_unique_jam_track.sql @@ -0,0 +1 @@ +DROP INDEX jam_track_tracks_position_uniqkey; \ No newline at end of file diff --git a/db/up/jam_track_id_to_varchar.sql b/db/up/jam_track_id_to_varchar.sql new file mode 100644 index 000000000..9a2c0cdfd --- /dev/null +++ b/db/up/jam_track_id_to_varchar.sql @@ -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); diff --git a/db/up/jam_track_playable_plays.sql b/db/up/jam_track_playable_plays.sql new file mode 100644 index 000000000..dfa95d07d --- /dev/null +++ b/db/up/jam_track_playable_plays.sql @@ -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; + + + diff --git a/db/up/recording_client_metadata.sql b/db/up/recording_client_metadata.sql new file mode 100644 index 000000000..f800f234a --- /dev/null +++ b/db/up/recording_client_metadata.sql @@ -0,0 +1 @@ +ALTER TABLE recordings ADD COLUMN timeline JSON; \ No newline at end of file diff --git a/db/up/shopping_cart_anonymous.sql b/db/up/shopping_cart_anonymous.sql new file mode 100644 index 000000000..e22cfeecd --- /dev/null +++ b/db/up/shopping_cart_anonymous.sql @@ -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); \ No newline at end of file diff --git a/db/up/user_reuse_card_and_reedem.sql b/db/up/user_reuse_card_and_reedem.sql new file mode 100644 index 000000000..2f2b811ea --- /dev/null +++ b/db/up/user_reuse_card_and_reedem.sql @@ -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; \ No newline at end of file diff --git a/ruby/README.md b/ruby/README.md index 302798539..4f85c80a6 100644 --- a/ruby/README.md +++ b/ruby/README.md @@ -7,5 +7,3 @@ Create development database 'jam_ruby' Once you've created your database, migrate it: `bundle exec jam_ruby up` - - diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index 84a7f9fde..51ed2e45d 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -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" diff --git a/ruby/lib/jam_ruby/jam_tracks_manager.rb b/ruby/lib/jam_ruby/jam_tracks_manager.rb index 68c788f87..9322c3dbd 100644 --- a/ruby/lib/jam_ruby/jam_tracks_manager.rb +++ b/ruby/lib/jam_ruby/jam_tracks_manager.rb @@ -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? diff --git a/ruby/lib/jam_ruby/models/anonymous_user.rb b/ruby/lib/jam_ruby/models/anonymous_user.rb new file mode 100644 index 000000000..0a843185a --- /dev/null +++ b/ruby/lib/jam_ruby/models/anonymous_user.rb @@ -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 diff --git a/ruby/lib/jam_ruby/models/jam_track.rb b/ruby/lib/jam_ruby/models/jam_track.rb index 646fd18ff..56341ff26 100644 --- a/ruby/lib/jam_ruby/models/jam_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track.rb @@ -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 diff --git a/ruby/lib/jam_ruby/models/jam_track_right.rb b/ruby/lib/jam_ruby/models/jam_track_right.rb index 4f8852341..f5abb1353 100644 --- a/ruby/lib/jam_ruby/models/jam_track_right.rb +++ b/ruby/lib/jam_ruby/models/jam_track_right.rb @@ -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 diff --git a/ruby/lib/jam_ruby/models/jam_track_track.rb b/ruby/lib/jam_ruby/models/jam_track_track.rb index 419b49c63..78255d07c 100644 --- a/ruby/lib/jam_ruby/models/jam_track_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track_track.rb @@ -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 diff --git a/ruby/lib/jam_ruby/models/mix.rb b/ruby/lib/jam_ruby/models/mix.rb index 66f0dd38f..d5c7bd364 100644 --- a/ruby/lib/jam_ruby/models/mix.rb +++ b/ruby/lib/jam_ruby/models/mix.rb @@ -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 diff --git a/ruby/lib/jam_ruby/models/music_session.rb b/ruby/lib/jam_ruby/models/music_session.rb index a29d0b2f9..e2954d9f3 100644 --- a/ruby/lib/jam_ruby/models/music_session.rb +++ b/ruby/lib/jam_ruby/models/music_session.rb @@ -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) diff --git a/ruby/lib/jam_ruby/models/playable_play.rb b/ruby/lib/jam_ruby/models/playable_play.rb index 4631bc4db..a04018689 100644 --- a/ruby/lib/jam_ruby/models/playable_play.rb +++ b/ruby/lib/jam_ruby/models/playable_play.rb @@ -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 diff --git a/ruby/lib/jam_ruby/models/recorded_jam_track_track.rb b/ruby/lib/jam_ruby/models/recorded_jam_track_track.rb index f77615beb..53bad104c 100644 --- a/ruby/lib/jam_ruby/models/recorded_jam_track_track.rb +++ b/ruby/lib/jam_ruby/models/recorded_jam_track_track.rb @@ -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 diff --git a/ruby/lib/jam_ruby/models/recording.rb b/ruby/lib/jam_ruby/models/recording.rb index c4537c94d..cdb72816a 100644 --- a/ruby/lib/jam_ruby/models/recording.rb +++ b/ruby/lib/jam_ruby/models/recording.rb @@ -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 diff --git a/ruby/lib/jam_ruby/models/shopping_cart.rb b/ruby/lib/jam_ruby/models/shopping_cart.rb index da1e567f6..b2bbc13ce 100644 --- a/ruby/lib/jam_ruby/models/shopping_cart.rb +++ b/ruby/lib/jam_ruby/models/shopping_cart.rb @@ -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 \ No newline at end of file diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index 37381a839..7a1ca3646 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -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 diff --git a/ruby/lib/jam_ruby/recurly_client.rb b/ruby/lib/jam_ruby/recurly_client.rb index 96c4df57e..6b12332a0 100644 --- a/ruby/lib/jam_ruby/recurly_client.rb +++ b/ruby/lib/jam_ruby/recurly_client.rb @@ -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?) diff --git a/ruby/lib/jam_ruby/resque/audiomixer.rb b/ruby/lib/jam_ruby/resque/audiomixer.rb index e0a3b065e..fa987b69f 100644 --- a/ruby/lib/jam_ruby/resque/audiomixer.rb +++ b/ruby/lib/jam_ruby/resque/audiomixer.rb @@ -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 diff --git a/ruby/spec/factories.rb b/ruby/spec/factories.rb index 18dbeb8bf..74f0fb924 100644 --- a/ruby/spec/factories.rb +++ b/ruby/spec/factories.rb @@ -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 diff --git a/ruby/spec/jam_ruby/models/jam_track_right_spec.rb b/ruby/spec/jam_ruby/models/jam_track_right_spec.rb index e1e99c3f2..530535f8c 100644 --- a/ruby/spec/jam_ruby/models/jam_track_right_spec.rb +++ b/ruby/spec/jam_ruby/models/jam_track_right_spec.rb @@ -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 diff --git a/ruby/spec/jam_ruby/models/jam_track_spec.rb b/ruby/spec/jam_ruby/models/jam_track_spec.rb index 063f3a029..10cec40cd 100644 --- a/ruby/spec/jam_ruby/models/jam_track_spec.rb +++ b/ruby/spec/jam_ruby/models/jam_track_spec.rb @@ -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) diff --git a/ruby/spec/jam_ruby/models/jam_track_track_spec.rb b/ruby/spec/jam_ruby/models/jam_track_track_spec.rb index bce440ad8..6fb4343f6 100644 --- a/ruby/spec/jam_ruby/models/jam_track_track_spec.rb +++ b/ruby/spec/jam_ruby/models/jam_track_track_spec.rb @@ -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 diff --git a/ruby/spec/jam_ruby/models/music_session_spec.rb b/ruby/spec/jam_ruby/models/music_session_spec.rb index 6273855d9..e152fed55 100644 --- a/ruby/spec/jam_ruby/models/music_session_spec.rb +++ b/ruby/spec/jam_ruby/models/music_session_spec.rb @@ -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 diff --git a/ruby/spec/jam_ruby/models/recording_spec.rb b/ruby/spec/jam_ruby/models/recording_spec.rb index 3d8515f29..7b64b2144 100644 --- a/ruby/spec/jam_ruby/models/recording_spec.rb +++ b/ruby/spec/jam_ruby/models/recording_spec.rb @@ -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, diff --git a/ruby/spec/jam_ruby/models/shopping_cart_spec.rb b/ruby/spec/jam_ruby/models/shopping_cart_spec.rb index db3f4d75c..9daf3d449 100644 --- a/ruby/spec/jam_ruby/models/shopping_cart_spec.rb +++ b/ruby/spec/jam_ruby/models/shopping_cart_spec.rb @@ -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 diff --git a/ruby/spec/jam_ruby/recurly_client_spec.rb b/ruby/spec/jam_ruby/recurly_client_spec.rb index 0245c91e9..5597f9894 100644 --- a/ruby/spec/jam_ruby/recurly_client_spec.rb +++ b/ruby/spec/jam_ruby/recurly_client_spec.rb @@ -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 diff --git a/ruby/spec/jam_ruby/resque/jam_tracks_builder_spec.rb b/ruby/spec/jam_ruby/resque/jam_tracks_builder_spec.rb index b737f574e..7ead35e3a 100644 --- a/ruby/spec/jam_ruby/resque/jam_tracks_builder_spec.rb +++ b/ruby/spec/jam_ruby/resque/jam_tracks_builder_spec.rb @@ -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) diff --git a/ruby/spec/support/utilities.rb b/ruby/spec/support/utilities.rb index cab08fab2..94294b86c 100644 --- a/ruby/spec/support/utilities.rb +++ b/ruby/spec/support/utilities.rb @@ -166,6 +166,10 @@ def app_config 20 # 20 seconds end + def one_free_jamtrack_per_user + true + end + private diff --git a/web/Gemfile b/web/Gemfile index 0405c352f..c7cb8debf 100644 --- a/web/Gemfile +++ b/web/Gemfile @@ -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) diff --git a/web/README.md b/web/README.md index 4548ce76b..975118c93 100644 --- a/web/README.md +++ b/web/README.md @@ -1,5 +1,5 @@ - Jasmine Javascript Unit Tests ============================= Open browser to localhost:3000/teaspoon + diff --git a/web/app/assets/images/content/bkg_home_guitar.jpg b/web/app/assets/images/content/bkg_home_guitar.jpg new file mode 100644 index 000000000..b67797dd8 Binary files /dev/null and b/web/app/assets/images/content/bkg_home_guitar.jpg differ diff --git a/web/app/assets/images/content/bkg_home_guitar_x.jpg b/web/app/assets/images/content/bkg_home_guitar_x.jpg new file mode 100644 index 000000000..e76ec6a83 Binary files /dev/null and b/web/app/assets/images/content/bkg_home_guitar_x.jpg differ diff --git a/web/app/assets/images/content/checkmark.png b/web/app/assets/images/content/checkmark.png index 8ce5e42ee..628f05a1f 100644 Binary files a/web/app/assets/images/content/checkmark.png and b/web/app/assets/images/content/checkmark.png differ diff --git a/web/app/assets/images/content/icon_shopping_cart.png b/web/app/assets/images/content/icon_shopping_cart.png index 24bf9b09f..511fe7c77 100644 Binary files a/web/app/assets/images/content/icon_shopping_cart.png and b/web/app/assets/images/content/icon_shopping_cart.png differ diff --git a/web/app/assets/images/content/shopping-cart.png b/web/app/assets/images/content/shopping-cart.png index eef1a4c69..62e6cc2fb 100644 Binary files a/web/app/assets/images/content/shopping-cart.png and b/web/app/assets/images/content/shopping-cart.png differ diff --git a/web/app/assets/images/up_arrow.png b/web/app/assets/images/up_arrow.png new file mode 100644 index 000000000..6f3c95d68 Binary files /dev/null and b/web/app/assets/images/up_arrow.png differ diff --git a/web/app/assets/javascripts/accounts.js b/web/app/assets/javascripts/accounts.js index 64501695d..5df29a6bd 100644 --- a/web/app/assets/javascripts/accounts.js +++ b/web/app/assets/javascripts/accounts.js @@ -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() { diff --git a/web/app/assets/javascripts/accounts_jamtracks.js.coffee b/web/app/assets/javascripts/accounts_jamtracks.js.coffee new file mode 100644 index 000000000..43e1e51b9 --- /dev/null +++ b/web/app/assets/javascripts/accounts_jamtracks.js.coffee @@ -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', $('You will need to reconfigure your audio device.') + handled = true + if !handled + @app.notifyServerError jqXHR, 'Unable to Create Session' + \ No newline at end of file diff --git a/web/app/assets/javascripts/accounts_profile_samples.js b/web/app/assets/javascripts/accounts_profile_samples.js index 0b5f36b8d..8e96446b4 100644 --- a/web/app/assets/javascripts/accounts_profile_samples.js +++ b/web/app/assets/javascripts/accounts_profile_samples.js @@ -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; } diff --git a/web/app/assets/javascripts/application.js b/web/app/assets/javascripts/application.js index 037c2c1b7..73a57daad 100644 --- a/web/app/assets/javascripts/application.js +++ b/web/app/assets/javascripts/application.js @@ -35,6 +35,7 @@ //= require jquery.browser //= require jquery.custom-protocol //= require jquery.exists +//= require jquery.payment //= require jstz //= require class //= require AAC_underscore diff --git a/web/app/assets/javascripts/checkout_order.js b/web/app/assets/javascripts/checkout_order.js new file mode 100644 index 000000000..9024f5745 --- /dev/null +++ b/web/app/assets/javascripts/checkout_order.js @@ -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); \ No newline at end of file diff --git a/web/app/assets/javascripts/checkout_payment.js b/web/app/assets/javascripts/checkout_payment.js new file mode 100644 index 000000000..7c40ced26 --- /dev/null +++ b/web/app/assets/javascripts/checkout_payment.js @@ -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(""); + + 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(""); + + 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(""); + + 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(""); + + 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(""); + + 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(""); + + 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(""); + + 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(""); + + 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(""); + + 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(""); + + 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(""); + + 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(""); + + 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(""); + + 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(""); + + 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(""); + 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(""); + 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(""); + 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(""); + 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(""); + + 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(""); + + 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(""); + } + else if (key == 'verification_value') { + $paymentMethod.find('#divCardVerify .error-text').remove(); + $paymentMethod.find('#divCardVerify').addClass("error").addClass("transparent"); + $paymentMethod.find('#card-verify').after(""); + } + 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(""); + } + 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(""); + } + 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(""); + } + 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); \ No newline at end of file diff --git a/web/app/assets/javascripts/checkout_signin.js b/web/app/assets/javascripts/checkout_signin.js index beb536d3d..22ee88060 100644 --- a/web/app/assets/javascripts/checkout_signin.js +++ b/web/app/assets/javascripts/checkout_signin.js @@ -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"; diff --git a/web/app/assets/javascripts/checkout_signin.js.coffee b/web/app/assets/javascripts/checkout_signin.js.coffee new file mode 100644 index 000000000..fd8cc5288 --- /dev/null +++ b/web/app/assets/javascripts/checkout_signin.js.coffee @@ -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, {}) \ No newline at end of file diff --git a/web/app/assets/javascripts/dialog/jamtrackLicenseDialog.js b/web/app/assets/javascripts/dialog/jamtrackLicenseDialog.js new file mode 100644 index 000000000..c74d71e86 --- /dev/null +++ b/web/app/assets/javascripts/dialog/jamtrackLicenseDialog.js @@ -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); \ No newline at end of file diff --git a/web/app/assets/javascripts/dialog/jamtrack_payment_history_dialog.js.coffee b/web/app/assets/javascripts/dialog/jamtrack_payment_history_dialog.js.coffee new file mode 100644 index 000000000..a73460c58 --- /dev/null +++ b/web/app/assets/javascripts/dialog/jamtrack_payment_history_dialog.js.coffee @@ -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 = "No payments found" + @tbody.append(tr); + + afterShow:() => + + showDialog:() => + @app.layout.showDialog(@dialogId) + + \ No newline at end of file diff --git a/web/app/assets/javascripts/dialog/loginRequiredDialog.js b/web/app/assets/javascripts/dialog/loginRequiredDialog.js new file mode 100644 index 000000000..6795614c1 --- /dev/null +++ b/web/app/assets/javascripts/dialog/loginRequiredDialog.js @@ -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); \ No newline at end of file diff --git a/web/app/assets/javascripts/everywhere/everywhere.js b/web/app/assets/javascripts/everywhere/everywhere.js index 4564cd9b0..16adb8b3f 100644 --- a/web/app/assets/javascripts/everywhere/everywhere.js +++ b/web/app/assets/javascripts/everywhere/everywhere.js @@ -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); diff --git a/web/app/assets/javascripts/fakeJamClient.js b/web/app/assets/javascripts/fakeJamClient.js index 7a15ffbb1..d0b640b8d 100644 --- a/web/app/assets/javascripts/fakeJamClient.js +++ b/web/app/assets/javascripts/fakeJamClient.js @@ -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 diff --git a/web/app/assets/javascripts/findSession.js b/web/app/assets/javascripts/findSession.js index f54feb866..d3c4638c2 100644 --- a/web/app/assets/javascripts/findSession.js +++ b/web/app/assets/javascripts/findSession.js @@ -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); } }) diff --git a/web/app/assets/javascripts/globals.js b/web/app/assets/javascripts/globals.js index fbaeed1a8..175a96fd6 100644 --- a/web/app/assets/javascripts/globals.js +++ b/web/app/assets/javascripts/globals.js @@ -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 = { diff --git a/web/app/assets/javascripts/homeScreen.js b/web/app/assets/javascripts/homeScreen.js index 997927dbd..ec7b86d50 100644 --- a/web/app/assets/javascripts/homeScreen.js +++ b/web/app/assets/javascripts/homeScreen.js @@ -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; + } + + }); }; diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index dee5d7443..19fb63e9b 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -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; }; diff --git a/web/app/assets/javascripts/jam_track_screen.js.coffee b/web/app/assets/javascripts/jam_track_screen.js.coffee new file mode 100644 index 000000000..d2f1f21a0 --- /dev/null +++ b/web/app/assets/javascripts/jam_track_screen.js.coffee @@ -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 '
There\'s no jamtracks.
' + 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: $('
Loading ...
') + 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() + + diff --git a/web/app/assets/javascripts/jam_track_utils.js.coffee b/web/app/assets/javascripts/jam_track_utils.js.coffee index 2935665cc..d3da3180a 100644 --- a/web/app/assets/javascripts/jam_track_utils.js.coffee +++ b/web/app/assets/javascripts/jam_track_utils.js.coffee @@ -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 diff --git a/web/app/assets/javascripts/jamkazam.js b/web/app/assets/javascripts/jamkazam.js index db0615510..2bc31b957 100644 --- a/web/app/assets/javascripts/jamkazam.js +++ b/web/app/assets/javascripts/jamkazam.js @@ -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}) diff --git a/web/app/assets/javascripts/jamtrack.js b/web/app/assets/javascripts/jamtrack.js deleted file mode 100644 index 57371b79e..000000000 --- a/web/app/assets/javascripts/jamtrack.js +++ /dev/null @@ -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("
There's no jamtracks.
") ; - } - - 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: $('
Loading ...
'), - 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); \ No newline at end of file diff --git a/web/app/assets/javascripts/jamtrack.js.coffee b/web/app/assets/javascripts/jamtrack.js.coffee new file mode 100644 index 000000000..95a1c89f0 --- /dev/null +++ b/web/app/assets/javascripts/jamtrack.js.coffee @@ -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 '
There\'s no jamtracks.
' + 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: $('
Loading ...
') + 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() + + diff --git a/web/app/assets/javascripts/jamtrack_landing.js.coffee b/web/app/assets/javascripts/jamtrack_landing.js.coffee new file mode 100644 index 000000000..760744e9f --- /dev/null +++ b/web/app/assets/javascripts/jamtrack_landing.js.coffee @@ -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 = "#{jamtrack.original_artist}" + $("#band_list").append("
  • #{artistLink}
  • ") + + # 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) => diff --git a/web/app/assets/javascripts/layout.js b/web/app/assets/javascripts/layout.js index 723e75650..ac49684e6 100644 --- a/web/app/assets/javascripts/layout.js +++ b/web/app/assets/javascripts/layout.js @@ -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; diff --git a/web/app/assets/javascripts/notificationPanel.js b/web/app/assets/javascripts/notificationPanel.js index 7c3e608de..c6e1f25bc 100644 --- a/web/app/assets/javascripts/notificationPanel.js +++ b/web/app/assets/javascripts/notificationPanel.js @@ -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; diff --git a/web/app/assets/javascripts/order.js b/web/app/assets/javascripts/order.js index ed5035826..23360c626 100644 --- a/web/app/assets/javascripts/order.js +++ b/web/app/assets/javascripts/order.js @@ -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(""); - 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(""); - 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(""); + 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(""); + } 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(""); + $paymentMethod.find('#card-verify').after(""); 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(""); + + 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() { diff --git a/web/app/assets/javascripts/playbackControls.js b/web/app/assets/javascripts/playbackControls.js index ce7d74075..d56a1e214 100644 --- a/web/app/assets/javascripts/playbackControls.js +++ b/web/app/assets/javascripts/playbackControls.js @@ -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; diff --git a/web/app/assets/javascripts/scheduled_session.js.erb b/web/app/assets/javascripts/scheduled_session.js.erb index 969f9449d..cf75275e1 100644 --- a/web/app/assets/javascripts/scheduled_session.js.erb +++ b/web/app/assets/javascripts/scheduled_session.js.erb @@ -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')); diff --git a/web/app/assets/javascripts/selectLocation.js b/web/app/assets/javascripts/selectLocation.js index 4d5b36711..93cde1b12 100644 --- a/web/app/assets/javascripts/selectLocation.js +++ b/web/app/assets/javascripts/selectLocation.js @@ -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 = ''; 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) { diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index b62d2737d..db44a05fa 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -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(); diff --git a/web/app/assets/javascripts/sessionModel.js b/web/app/assets/javascripts/sessionModel.js index c09f4e878..05f726557 100644 --- a/web/app/assets/javascripts/sessionModel.js +++ b/web/app/assets/javascripts/sessionModel.js @@ -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; diff --git a/web/app/assets/javascripts/shopping_cart.js b/web/app/assets/javascripts/shopping_cart.js index 4a7d2bf7e..86157b96d 100644 --- a/web/app/assets/javascripts/shopping_cart.js +++ b/web/app/assets/javascripts/shopping_cart.js @@ -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'; + } + }) } } diff --git a/web/app/assets/javascripts/sidebar.js b/web/app/assets/javascripts/sidebar.js index d60d59868..cf733c451 100644 --- a/web/app/assets/javascripts/sidebar.js +++ b/web/app/assets/javascripts/sidebar.js @@ -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; diff --git a/web/app/assets/javascripts/site_validator.js.coffee b/web/app/assets/javascripts/site_validator.js.coffee index 2c63c7ed7..fc54465dd 100644 --- a/web/app/assets/javascripts/site_validator.js.coffee +++ b/web/app/assets/javascripts/site_validator.js.coffee @@ -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') diff --git a/web/app/assets/javascripts/utils.js b/web/app/assets/javascripts/utils.js index aee15f5c6..d4d91e6cb 100644 --- a/web/app/assets/javascripts/utils.js +++ b/web/app/assets/javascripts/utils.js @@ -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; diff --git a/web/app/assets/javascripts/web/downloads.js b/web/app/assets/javascripts/web/downloads.js index 82ef0a1da..6f01037e5 100644 --- a/web/app/assets/javascripts/web/downloads.js +++ b/web/app/assets/javascripts/web/downloads.js @@ -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!
    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'); diff --git a/web/app/assets/stylesheets/client/account.css.scss b/web/app/assets/stylesheets/client/account.css.scss index 782f43065..49bbb7c33 100644 --- a/web/app/assets/stylesheets/client/account.css.scss +++ b/web/app/assets/stylesheets/client/account.css.scss @@ -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; diff --git a/web/app/assets/stylesheets/client/accountProfileSamples.css.scss b/web/app/assets/stylesheets/client/accountProfileSamples.css.scss index 1e0b4caa1..e5f1e310b 100644 --- a/web/app/assets/stylesheets/client/accountProfileSamples.css.scss +++ b/web/app/assets/stylesheets/client/accountProfileSamples.css.scss @@ -25,6 +25,10 @@ .sample { margin: 3px 5px 10px 0px; + + a.add-recording-source { + margin-top: 2px !important; + } } .sample-list { diff --git a/web/app/assets/stylesheets/client/checkout.css.scss b/web/app/assets/stylesheets/client/checkout.css.scss index efe918e22..7d5190ad8 100644 --- a/web/app/assets/stylesheets/client/checkout.css.scss +++ b/web/app/assets/stylesheets/client/checkout.css.scss @@ -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; - } - } -} \ No newline at end of file diff --git a/web/app/assets/stylesheets/client/checkout_order.css.scss b/web/app/assets/stylesheets/client/checkout_order.css.scss new file mode 100644 index 000000000..92db40291 --- /dev/null +++ b/web/app/assets/stylesheets/client/checkout_order.css.scss @@ -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; + } + } + } +} \ No newline at end of file diff --git a/web/app/assets/stylesheets/client/checkout_payment.css.scss b/web/app/assets/stylesheets/client/checkout_payment.css.scss new file mode 100644 index 000000000..39ddaccbf --- /dev/null +++ b/web/app/assets/stylesheets/client/checkout_payment.css.scss @@ -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; + } + } + } +} \ No newline at end of file diff --git a/web/app/assets/stylesheets/client/checkout_signin.css.scss b/web/app/assets/stylesheets/client/checkout_signin.css.scss new file mode 100644 index 000000000..d436c5c58 --- /dev/null +++ b/web/app/assets/stylesheets/client/checkout_signin.css.scss @@ -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; + } +} \ No newline at end of file diff --git a/web/app/assets/stylesheets/client/client.css b/web/app/assets/stylesheets/client/client.css index c953408ec..28049797e 100644 --- a/web/app/assets/stylesheets/client/client.css +++ b/web/app/assets/stylesheets/client/client.css @@ -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 diff --git a/web/app/assets/stylesheets/client/common.css.scss b/web/app/assets/stylesheets/client/common.css.scss index a349729f5..69f430b7f 100644 --- a/web/app/assets/stylesheets/client/common.css.scss +++ b/web/app/assets/stylesheets/client/common.css.scss @@ -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; + } +} diff --git a/web/app/assets/stylesheets/client/content.css.scss b/web/app/assets/stylesheets/client/content.css.scss index 9bee28a72..f5bad0e14 100644 --- a/web/app/assets/stylesheets/client/content.css.scss +++ b/web/app/assets/stylesheets/client/content.css.scss @@ -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; } } diff --git a/web/app/assets/stylesheets/client/flash.css.scss b/web/app/assets/stylesheets/client/flash.css.scss new file mode 100644 index 000000000..918f3b014 --- /dev/null +++ b/web/app/assets/stylesheets/client/flash.css.scss @@ -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; + } + +} diff --git a/web/app/assets/stylesheets/client/home.css.scss b/web/app/assets/stylesheets/client/home.css.scss index d9b7d9d45..c4e7c07f7 100644 --- a/web/app/assets/stylesheets/client/home.css.scss +++ b/web/app/assets/stylesheets/client/home.css.scss @@ -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); diff --git a/web/app/assets/stylesheets/client/jamkazam.css.scss b/web/app/assets/stylesheets/client/jamkazam.css.scss index d777d3f11..d1e2b4192 100644 --- a/web/app/assets/stylesheets/client/jamkazam.css.scss +++ b/web/app/assets/stylesheets/client/jamkazam.css.scss @@ -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; } diff --git a/web/app/assets/stylesheets/client/jamtrack.css.scss b/web/app/assets/stylesheets/client/jamtrack.css.scss index 7d69b1b6f..d63839387 100644 --- a/web/app/assets/stylesheets/client/jamtrack.css.scss +++ b/web/app/assets/stylesheets/client/jamtrack.css.scss @@ -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 } \ No newline at end of file diff --git a/web/app/assets/stylesheets/client/session.css.scss b/web/app/assets/stylesheets/client/session.css.scss index 42467d158..00fde3ac1 100644 --- a/web/app/assets/stylesheets/client/session.css.scss +++ b/web/app/assets/stylesheets/client/session.css.scss @@ -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; diff --git a/web/app/assets/stylesheets/client/sessionList.css.scss b/web/app/assets/stylesheets/client/sessionList.css.scss index b0df3def5..ce326ef9c 100644 --- a/web/app/assets/stylesheets/client/sessionList.css.scss +++ b/web/app/assets/stylesheets/client/sessionList.css.scss @@ -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; diff --git a/web/app/assets/stylesheets/client/shoppingCart.css.scss b/web/app/assets/stylesheets/client/shoppingCart.css.scss index f8a8c6fdb..ba3b31146 100644 --- a/web/app/assets/stylesheets/client/shoppingCart.css.scss +++ b/web/app/assets/stylesheets/client/shoppingCart.css.scss @@ -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 { diff --git a/web/app/assets/stylesheets/client/sidebar.css.scss b/web/app/assets/stylesheets/client/sidebar.css.scss index 17c975616..59a3bdfb5 100644 --- a/web/app/assets/stylesheets/client/sidebar.css.scss +++ b/web/app/assets/stylesheets/client/sidebar.css.scss @@ -5,6 +5,10 @@ background-color: $ColorElementPrimary; + &.not-logged-in { + opacity:0.6; + } + .panel-header { margin:0px; padding:0px; diff --git a/web/app/assets/stylesheets/client/site_validator.css.scss b/web/app/assets/stylesheets/client/site_validator.css.scss index 40f3ac22f..09853cdae 100644 --- a/web/app/assets/stylesheets/client/site_validator.css.scss +++ b/web/app/assets/stylesheets/client/site_validator.css.scss @@ -24,7 +24,7 @@ display:inline-block; vertical-align: middle; position: relative; - margin-top: -40px; + margin-top: -10px; left: 0px; } .error { diff --git a/web/app/assets/stylesheets/dialogs/loginRequiredDialog.css.scss b/web/app/assets/stylesheets/dialogs/loginRequiredDialog.css.scss new file mode 100644 index 000000000..bc27a60ed --- /dev/null +++ b/web/app/assets/stylesheets/dialogs/loginRequiredDialog.css.scss @@ -0,0 +1,12 @@ +#login-required-dialog { + + width:455px; + + p { + margin:0 0 20px 0; + } + + .buttons { + margin-top:20px; + } +} \ No newline at end of file diff --git a/web/app/assets/stylesheets/landings/landing_page.css.scss b/web/app/assets/stylesheets/landings/landing_page.css.scss index ce9f7496b..56d1ad837 100644 --- a/web/app/assets/stylesheets/landings/landing_page.css.scss +++ b/web/app/assets/stylesheets/landings/landing_page.css.scss @@ -9,24 +9,6 @@ body.web.landing_page { display:none; } - .badge-number { - font-size:125%; - color:white; - background-color:$ColorScreenPrimary; - width:30px; - height:30px; - //position:absolute; - //left:-10px; - //top:4px; - margin-right:10px; - -webkit-border-radius:50%; - -moz-border-radius:50%; - border-radius:50%; - text-align:center; - display:inline-block; - border:2px solid white; - } - &.kick { h1 { diff --git a/web/app/assets/stylesheets/web/audioWidgets.css.scss b/web/app/assets/stylesheets/web/audioWidgets.css.scss index 5a6fb3303..4c044f8b7 100644 --- a/web/app/assets/stylesheets/web/audioWidgets.css.scss +++ b/web/app/assets/stylesheets/web/audioWidgets.css.scss @@ -30,6 +30,8 @@ &.metronome-mode { width:200px; + margin-left:-100px; + left:50%; .recording-position, .recording-current, .playback-mode-buttons { display:none; } diff --git a/web/app/assets/stylesheets/web/downloads.css.scss b/web/app/assets/stylesheets/web/downloads.css.scss new file mode 100644 index 000000000..4634d2f04 --- /dev/null +++ b/web/app/assets/stylesheets/web/downloads.css.scss @@ -0,0 +1,131 @@ +@import "client/common.css.scss"; + +body.downloads { + + + h2 { + font-size:22px; + color:white !important; + } + + ul { + width:auto !important; + } + + ul li { + color:#CCC; + font-size:14px; + line-height:125%; + text-indent:-5px; + margin-left:40px; + + &:before + { content:"-"; + position:relative; + left:-5px; + } + } + + p { + margin:20px 0 10px; + line-height:125% !important; + width:100% !important; + } + + .download-app { + + padding-top:20px; + width:55%; + float:left; + @include border_box_sizing; + } + + .jamtracks { + + padding-top:20px; + float:left; + width:45%; + @include border_box_sizing; + padding-left:5%; + + .shop-jamtracks { + + } + + .special-value { + font-size:16px; + } + } + + .video-container { + position:relative; + width:320px; + height:180px; + } + + .video-container iframe, + .video-container object, + .video-container embed { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + } + + .download-content { + border-style:solid; + border-width:0 1px 0 0; + border-color:#ccc; + } + .download-entreaty { + + + p.click-to-download { + margin-bottom:5px; + } + + } + + .downloads-container { + position:relative; + display:inline-block; + height:92px; + top:-25px; + left:20px; + } + .downloads-blurb { + + h5 { + color:#ccc; + margin-bottom:5px; + margin-top:-15px; + } + + ul li { + font-size:12px; + margin-left:15px; + } + + .choose-other-platform {line-height:125%;} + + } + + .downloads { + position:relative; + } + + .download-box { + color:#fff; + text-align:center; + } + + .go-jamtrack-shopping { + line-height: 125%; + text-align: center; + display: block; + position: absolute; + right: 0; + bottom: 90px; + } +} diff --git a/web/app/assets/stylesheets/web/main.css.scss b/web/app/assets/stylesheets/web/main.css.scss index 82c5160fd..2a2891d60 100644 --- a/web/app/assets/stylesheets/web/main.css.scss +++ b/web/app/assets/stylesheets/web/main.css.scss @@ -48,6 +48,16 @@ body.web { margin-left:70px; width:400px; + &.no-user-dropdown { + margin-left:0; + margin-top:30px; + position:absolute; + top:0; + right:0; + text-align:center; + width:350px; + } + h1 { color:#ed3618; font-size:26px; @@ -310,15 +320,29 @@ body.web { .register-page { .register-container { - padding:10px; + padding:0 10px 10px; } input.register-musician { } + .create-account-header { + padding-top:10px; + margin-left:10px; + } + .actions { margin-top:20px; + input, a { + font-size:18px; + } + input { + margin-left:2px; + } + a { + margin-left:10px; + } } .error { @@ -327,6 +351,10 @@ body.web { margin-right:-12px; } + div.register-as { + margin-left:45px; + } + input.register-fan { margin-left:20px; } @@ -415,39 +443,11 @@ body.web { font-weight:normal; } - .download-box { - padding:15px; - color:#fff; - text-align:center; - font-size:20px; - -webkit-border-radius: 8px; - border-radius: 8px; - background:#383838; - margin-top:100px; - } - - .system-requirements { - margin-top:35px; - display:none; - - ul { - display:none; - list-style:disc outside none; - padding-left:40px; - } - } div.proceed { margin:40px 0 250px 5px; } - .downloads { - ul { - list-style:disc outside none; - padding-left:40px; - } - } - .congratulations { padding-top:1px; } diff --git a/web/app/assets/stylesheets/web/web.css b/web/app/assets/stylesheets/web/web.css index d91e79de2..93d52b7c2 100644 --- a/web/app/assets/stylesheets/web/web.css +++ b/web/app/assets/stylesheets/web/web.css @@ -13,6 +13,7 @@ *= require client/hoverBubble *= require client/help *= require client/listenBroadcast +*= require client/flash *= require web/main *= require web/footer *= require web/recordings @@ -20,6 +21,7 @@ #= require web/sessions *= require web/events *= require web/session_info +*= require web/downloads *= require users/signinCommon *= require dialogs/dialog *= require landings/landing_page diff --git a/web/app/controllers/api_genres_controller.rb b/web/app/controllers/api_genres_controller.rb index c413bf1f2..293552737 100644 --- a/web/app/controllers/api_genres_controller.rb +++ b/web/app/controllers/api_genres_controller.rb @@ -1,8 +1,5 @@ class ApiGenresController < ApiController - # have to be signed in currently to see this screen - before_filter :api_signed_in_user - respond_to :json def index diff --git a/web/app/controllers/api_instruments_controller.rb b/web/app/controllers/api_instruments_controller.rb index fc2c44fd9..c5f10bd49 100644 --- a/web/app/controllers/api_instruments_controller.rb +++ b/web/app/controllers/api_instruments_controller.rb @@ -1,8 +1,5 @@ class ApiInstrumentsController < ApiController - # have to be signed in currently to see this screen - before_filter :api_signed_in_user - respond_to :json def index diff --git a/web/app/controllers/api_jam_tracks_controller.rb b/web/app/controllers/api_jam_tracks_controller.rb index eff17afa0..f1b4264fa 100644 --- a/web/app/controllers/api_jam_tracks_controller.rb +++ b/web/app/controllers/api_jam_tracks_controller.rb @@ -1,18 +1,41 @@ class ApiJamTracksController < ApiController # have to be signed in currently to see this screen - before_filter :api_signed_in_user + before_filter :api_signed_in_user, :except => [:index] + before_filter :api_any_user, :only => [:index] before_filter :lookup_jam_track_right, :only => [:download,:enqueue, :show_jam_track_right] respond_to :json def index - data = JamTrack.index(params, current_user) + data = JamTrack.index(params, any_user) @jam_tracks, @next = data[0], data[1] render "api_jam_tracks/index", :layout => nil end + def played + if params[:id].blank? + render(:json => { :message => "JamTrack ID required" }, :status => 400) and return + end + play = PlayablePlay.new + play.player_id = current_user.id + play.ip_address = request.remote_ip + + # VRFS-2916 jam_tracks.id is varchar: REMOVE + # play.jam_track = JamTrack.where(id: params[:id].to_i).first + # VRFS-2916 jam_tracks.id is varchar: ADD + play.playable = JamTrack.where(id: params[:id]).first + + play.save + + if play.errors.any? + render :json => { :message => "Unexpected error occurred" }, :status => 500 + else + render :json => {}, :status => 201 + end + end + def purchased params[:show_purchased_only] = true data = JamTrack.index(params, current_user) @@ -23,7 +46,8 @@ class ApiJamTracksController < ApiController render "api_jam_tracks/purchased", :layout => nil end - def download + + def download if @jam_track_right.valid? sample_rate = params[:sample_rate].nil? ? nil : params[:sample_rate].to_i if (@jam_track_right && @jam_track_right.ready?(sample_rate)) diff --git a/web/app/controllers/api_recordings_controller.rb b/web/app/controllers/api_recordings_controller.rb index b6f246e72..27eb6c856 100644 --- a/web/app/controllers/api_recordings_controller.rb +++ b/web/app/controllers/api_recordings_controller.rb @@ -1,7 +1,7 @@ class ApiRecordingsController < ApiController before_filter :api_signed_in_user, :except => [ :add_like ] - before_filter :lookup_recording, :only => [ :show, :stop, :claim, :discard, :keep, :delete_claim, :add_timeline_data ] + before_filter :lookup_recording, :only => [ :show, :stop, :claim, :discard, :keep, :delete_claim, :add_timeline ] before_filter :lookup_recorded_track, :only => [ :download, :upload_next_part, :upload_sign, :upload_part_complete, :upload_complete ] before_filter :lookup_recorded_backing_track, :only => [ :backing_track_download, :backing_track_upload_next_part, :backing_track_upload_sign, :backing_track_upload_part_complete, :backing_track_upload_complete ] before_filter :lookup_recorded_video, :only => [ :video_upload_sign, :video_upload_start, :video_upload_complete ] @@ -379,7 +379,7 @@ class ApiRecordingsController < ApiController # metadata def add_timeline - @recording.add_timeline(params[:metadata]) + @recording.add_timeline(params) render :json => {}, :status => 200 end diff --git a/web/app/controllers/api_recurly_controller.rb b/web/app/controllers/api_recurly_controller.rb index 50f4f703d..480e33ee8 100644 --- a/web/app/controllers/api_recurly_controller.rb +++ b/web/app/controllers/api_recurly_controller.rb @@ -1,15 +1,70 @@ require 'jam_ruby/recurly_client' class ApiRecurlyController < ApiController - before_filter :api_signed_in_user + before_filter :api_signed_in_user, :except => [:create_account] before_filter :create_client respond_to :json # create Recurly account def create_account - @account = @client.find_or_create_account(current_user, params[:billing_info]) - render :json=>account_json(@account) - rescue RecurlyClientError => x - render json: { :message => x.inspect, errors: x.errors }, :status => 404 + + billing_info = params[:billing_info] + shipping_info = params[:shipping_info] + # should we let the user reuse this card next time? + reuse_card_next_time = params[:reuse_card_next_time] == "true" + # should we update the card info, or use what's on file this time? + reuse_card_this_time = params[:reuse_card_this_time] == "true" + # terms of service accepted? + terms_of_service = params[:terms_of_service] == "true" + + + if current_user + # keep reuse card up-to-date + User.where(id: current_user.id).update_all(reuse_card: params[:reuse_card_next_time]) + else + options = { + remote_ip: request.remote_ip, + first_name: billing_info[:first_name], + last_name: billing_info[:last_name], + email: params[:email], + password: params[:password], + password_confirmation: params[:password], + terms_of_service: terms_of_service, + instruments: [{ :instrument_id => 'other', :proficiency_level => 1, :priority => 1 }], + birth_date: nil, + location: { :country => billing_info[:country], :state => billing_info[:state], :city => billing_info[:city]}, + musician: true, + skip_recaptcha: true, + invited_user: nil, + fb_signup: nil, + signup_confirm_url: ApplicationHelper.base_uri(request) + "/confirm", + any_user: any_user, + reuse_card: reuse_card_next_time + } + + user = UserManager.new.signup(options) + + if user.errors.any? + # render any @user.errors on error + respond_with_model(user) + return + else + sign_in user + end + end + + begin + billing_info[:ip_address] = request.remote_ip if billing_info + if reuse_card_this_time + # do not attempt to update any billing/shipping info unless the user re-inputs their info! + @account = @client.get_account(current_user) + else + @account = @client.find_or_create_account(current_user, billing_info) + end + + render :json=>account_json(@account) + rescue RecurlyClientError => x + render json: { :message => x.inspect, errors: x.errors }, :status => 404 + end end def delete_account @@ -21,13 +76,21 @@ class ApiRecurlyController < ApiController # get Recurly account def get_account - @account=@client.get_account(current_user) + @account = @client.get_account(current_user) render :json=>account_json(@account) rescue RecurlyClientError => e render json: { message: x.inspect, errors: x.errors}, :status => 404 end + # get Recurly payment history + def payment_history + @payments=@client.payment_history(current_user) + render :json=>{payments: @payments} + rescue RecurlyClientError => x + render json: { message: x.inspect, errors: x.errors}, :status => 404 + end + # update Recurly account def update_account @account=@client.update_account(current_user, params[:billing_info]) @@ -39,9 +102,11 @@ class ApiRecurlyController < ApiController # get Billing Information def billing_info @account = @client.get_account(current_user) - # @billing = @account.billing_info - # @billing ||= @account - render :json=> account_json(@account) + if @account + render :json=> account_json(@account) + else + render :json=> {}, :status => 404 + end rescue RecurlyClientError => x render json: { message: x.inspect, errors: x.errors}, :status => 404 end @@ -58,30 +123,18 @@ class ApiRecurlyController < ApiController error=nil response = {jam_tracks:[]} - # 1st confirm that all specified JamTracks exist - jam_tracks = [] + current_user.shopping_carts.each do |shopping_cart| + jam_track = shopping_cart.cart_product - params[:jam_tracks].each do |jam_track_id| - jam_track = JamTrack.where("id=?", jam_track_id).first - if jam_track - jam_tracks << jam_track - else - error="JamTrack not found for '#{jam_track_id}'" - break - end + # if shopping_cart has any marked_for_redeem, then we zero out the price by passing in 'free' + # NOTE: shopping_carts have the idea of quantity, but you should only be able to buy at most one JamTrack. So anything > 0 is considered free for a JamTrack + + jam_track_right = @client.place_order(current_user, jam_track, shopping_cart) + # build up the response object with JamTracks that were purchased. + # if this gets more complicated, we should switch to RABL + response[:jam_tracks] << {name: jam_track.name, id: jam_track.id, jam_track_right_id: jam_track_right.id, version: jam_track.version} end - # then buy each - unless error - jam_tracks.each do |jam_track| - jam_track_right = @client.place_order(current_user, jam_track) - # build up the response object with JamTracks that were purchased. - # if this gets more complicated, we should switch to RABL - response[:jam_tracks] << {name: jam_track.name, id: jam_track.id, jam_track_right_id: jam_track_right.id, version: jam_track.version} - end - end - - if error render json: { errors: {message:error}}, :status => 404 else @@ -97,16 +150,21 @@ private end def account_json(account) + + billing_info = account.billing_info.nil? ? nil : { + :first_name => account.billing_info.first_name, + :last_name => account.billing_info.last_name, + :address1 => account.billing_info.address1, + :address2 => account.billing_info.address2, + :city => account.billing_info.city, + :state => account.billing_info.state, + :zip => account.billing_info.zip, + :country => account.billing_info.country, + :last_four => account.billing_info.last_four + } + { - :first_name => account.first_name, - :last_name => account.last_name, - :email => account.email, - :address1 => account.billing_info ? account.billing_info.address1 : nil, - :address2 => account.billing_info ? account.billing_info.address2 : nil, - :city => account.billing_info ? account.billing_info.city : nil, - :state => account.billing_info ? account.billing_info.state : nil, - :zip => account.billing_info ? account.billing_info.zip : nil, - :country => account.billing_info ? account.billing_info.country : nil + billing_info: billing_info } end diff --git a/web/app/controllers/api_shopping_carts_controller.rb b/web/app/controllers/api_shopping_carts_controller.rb index 5cff85b97..761c3dc81 100644 --- a/web/app/controllers/api_shopping_carts_controller.rb +++ b/web/app/controllers/api_shopping_carts_controller.rb @@ -1,11 +1,11 @@ class ApiShoppingCartsController < ApiController - before_filter :api_signed_in_user + before_filter :api_any_user respond_to :json def index - @carts = current_user.shopping_carts + @carts = any_user.shopping_carts end def add_jamtrack @@ -16,7 +16,7 @@ class ApiShoppingCartsController < ApiController raise StateError, "Invalid JamTrack." end - @cart = ShoppingCart.create current_user, jam_track + @cart = ShoppingCart.add_jam_track_to_cart(any_user, jam_track) if @cart.errors.any? response.status = :unprocessable_entity @@ -43,15 +43,16 @@ class ApiShoppingCartsController < ApiController end def remove_cart - @cart = current_user.shopping_carts.find_by_id(params[:id]) + @cart = any_user.shopping_carts.find_by_id(params[:id]) raise StateError, "Invalid Cart." if @cart.nil? - @cart.destroy + ShoppingCart.remove_jam_track_from_cart(any_user, @cart) + respond_with responder: ApiResponder, :status => 204 end def clear_all - ShoppingCart.where("user_id=?", current_user).destroy_all + any_user.destroy_all_shopping_carts render :json=>{}, :status=>200 end diff --git a/web/app/controllers/api_users_controller.rb b/web/app/controllers/api_users_controller.rb index 244dbc6af..a6ebb81c3 100644 --- a/web/app/controllers/api_users_controller.rb +++ b/web/app/controllers/api_users_controller.rb @@ -26,7 +26,7 @@ class ApiUsersController < ApiController @user = User.includes([{musician_instruments: :instrument}, {band_musicians: :user}, {genre_players: :genre}, - :bands, :instruments, :genres]) + :bands, :instruments, :genres, :jam_track_rights]) .find(params[:id]) respond_with @user, responder: ApiResponder, :status => 200 diff --git a/web/app/controllers/application_controller.rb b/web/app/controllers/application_controller.rb index 29e294ac5..4a2ae2397 100644 --- a/web/app/controllers/application_controller.rb +++ b/web/app/controllers/application_controller.rb @@ -12,6 +12,8 @@ class ApplicationController < ActionController::Base gon_setup end + before_filter :set_tracking_cookie + before_filter do if params[AffiliatePartner::PARAM_REFERRAL].present? && current_user.nil? if cookies[AffiliatePartner::PARAM_COOKIE].blank? @@ -25,6 +27,12 @@ class ApplicationController < ActionController::Base cookies[AffiliatePartner::PARAM_COOKIE] end + + # http://stackoverflow.com/questions/15807214/where-to-set-a-tracking-permanent-cookie-in-rails + def set_tracking_cookie + cookies.permanent[:user_uuid] = SecureRandom.uuid unless cookies[:user_uuid] + end + private def add_user_info_to_bugsnag(notif) # Add some app-specific data which will be displayed on a custom diff --git a/web/app/controllers/users_controller.rb b/web/app/controllers/users_controller.rb index 61c42fe28..bcac17d3e 100644 --- a/web/app/controllers/users_controller.rb +++ b/web/app/controllers/users_controller.rb @@ -37,6 +37,7 @@ class UsersController < ApplicationController end def new + @no_user_dropdown = true if current_user redirect_to client_url return @@ -175,14 +176,17 @@ class UsersController < ApplicationController end def congratulations_fan + @no_user_dropdown = true render :layout => "web" end def congratulations_musician + @no_user_dropdown = true render :layout => "web" end def downloads + @no_user_dropdown = true render :layout => "web" end diff --git a/web/app/helpers/sessions_helper.rb b/web/app/helpers/sessions_helper.rb index 3545c3d05..5232fc24c 100644 --- a/web/app/helpers/sessions_helper.rb +++ b/web/app/helpers/sessions_helper.rb @@ -26,6 +26,10 @@ module SessionsHelper !current_user.nil? end + def has_anonymous_user? + !anonymous_user.nil? + end + def current_user=(user) @current_user = user end @@ -34,6 +38,27 @@ module SessionsHelper @current_user ||= User.find_by_remember_token(cookies[:remember_token]) end + def anonymous_user=(anonymous_user) + @anonymous_user = anonymous_user + end + + def anonymous_user + if anon_cookie + @anonymous_user ||= AnonymousUser.new(anon_cookie) + else + nil + end + end + + # tries current_user over anonymous_user + def any_user + current_user || anonymous_user + end + + def anon_cookie + @anon_cookie ||= cookies[:user_uuid] + end + def current_user?(user) user == current_user end @@ -45,13 +70,19 @@ module SessionsHelper end end - def api_signed_in_user unless signed_in? render :json => { :message => "not logged in"}, :status => 403 end end + # take either the signed in user, or if that fails, try the anonymous user + def api_any_user + unless signed_in? || has_anonymous_user? + render :json => { :message => "not logged in"}, :status => 403 + end + end + def sign_out current_user = nil cookies.delete(:remember_token, domain: Rails.application.config.session_cookie_domain) @@ -92,6 +123,11 @@ module SessionsHelper end end + def logged_in_not_logged_in_class + signed_in? ? "logged-in" : "not-logged-in" + end + + def metronome_tempos [ 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 63, 66, 69, 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116, 120, 126, 132, 138, 144, 152, 160, 168, 176, 184, 192, 200, 208 diff --git a/web/app/views/api_claimed_recordings/show.rabl b/web/app/views/api_claimed_recordings/show.rabl index 91174f87b..c22a49ad3 100644 --- a/web/app/views/api_claimed_recordings/show.rabl +++ b/web/app/views/api_claimed_recordings/show.rabl @@ -22,6 +22,10 @@ end child(:recording => :recording) { attributes :id, :created_at, :duration, :comment_count, :like_count, :play_count, :jam_track_id, :jam_track_initiator_id + node :timeline do |recording| + recording.timeline ? JSON.parse(recording.timeline) : {} + end + child(:jam_track => :jam_track) { attributes :id diff --git a/web/app/views/api_jam_tracks/show.rabl b/web/app/views/api_jam_tracks/show.rabl index 184f44c89..e4226b553 100644 --- a/web/app/views/api_jam_tracks/show.rabl +++ b/web/app/views/api_jam_tracks/show.rabl @@ -7,7 +7,11 @@ node :genres do |item| end node :added_cart do |item| - current_user.shopping_carts.where("cart_id='?'",item.id).count != 0 + any_user.shopping_carts.where(cart_id: item.id).count != 0 +end + +node :purchased do |item| + !!item.right_for_user(current_user) end child(:jam_track_tracks => :tracks) { diff --git a/web/app/views/api_jam_tracks/show_for_client.rabl b/web/app/views/api_jam_tracks/show_for_client.rabl index faf84c144..bc212f46d 100644 --- a/web/app/views/api_jam_tracks/show_for_client.rabl +++ b/web/app/views/api_jam_tracks/show_for_client.rabl @@ -1,6 +1,10 @@ object @jam_track -attributes :id, :name, :description, :initial_play_silence, :original_artist, :version +attributes :id, :name, :description, :initial_play_silence, :original_artist, :version, :genre + +node :genre do |jam_track| + jam_track.genre.present? ? jam_track.genre.id : nil +end node :jmep do |jam_track| jam_track.jmep_json ? JSON.parse(jam_track.jmep_json) : nil diff --git a/web/app/views/api_music_sessions/show.rabl b/web/app/views/api_music_sessions/show.rabl index 8149352d0..093e47077 100644 --- a/web/app/views/api_music_sessions/show.rabl +++ b/web/app/views/api_music_sessions/show.rabl @@ -79,10 +79,6 @@ else child(:jam_track_tracks => :tracks) { attributes :id, :part, :instrument, :track_type } - - child(:jam_track_tap_ins => :tap_ins) { - attributes :offset_time, :bpm, :tap_in_count - } } # only show currently playing recording data if the current_user is in the session @@ -107,6 +103,10 @@ else end } + child({:jam_track => :jam_track}) { + attributes :id, :name, :description + } + child(:recorded_tracks => :recorded_tracks) { attributes :id, :fully_uploaded, :client_track_id, :client_id, :instrument_id @@ -126,6 +126,19 @@ else attributes :id, :first_name, :last_name, :city, :state, :country, :photo_url } } + + child(:recorded_jam_track_tracks => :recorded_jam_track_tracks) { + node do |recorded_jam_track_track| + { + id: recorded_jam_track_track.jam_track_track.id, + user_id: recorded_jam_track_track.user_id, + part: recorded_jam_track_track.jam_track_track.part, + instrument: recorded_jam_track_track.jam_track_track.instrument, + track_type: recorded_jam_track_track.jam_track_track.track_type, + timeline: recorded_jam_track_track.timeline ? JSON.parse(recorded_jam_track_track.timeline) : [] + } + end + } } } diff --git a/web/app/views/api_recordings/show.rabl b/web/app/views/api_recordings/show.rabl index a717a6bfb..330f12f7b 100644 --- a/web/app/views/api_recordings/show.rabl +++ b/web/app/views/api_recordings/show.rabl @@ -12,6 +12,10 @@ node :mix do |recording| end end +node :timeline do |recording| + recording.timeline ? JSON.parse(recording.timeline) : {} +end + child(:jam_track => :jam_track) { attributes :id diff --git a/web/app/views/api_users/show.rabl b/web/app/views/api_users/show.rabl index 03e38f321..26dc5df11 100644 --- a/web/app/views/api_users/show.rabl +++ b/web/app/views/api_users/show.rabl @@ -1,6 +1,6 @@ object @user -attributes :id, :first_name, :last_name, :name, :city, :state, :country, :location, :online, :photo_url, :musician, :gender, :birth_date, :internet_service_provider, :friend_count, :liker_count, :like_count, :follower_count, :following_count, :recording_count, :session_count, :biography, :favorite_count, :audio_latency, :upcoming_session_count, :age, :website, :skill_level +attributes :id, :first_name, :last_name, :name, :city, :state, :country, :location, :online, :photo_url, :musician, :gender, :birth_date, :internet_service_provider, :friend_count, :liker_count, :like_count, :follower_count, :following_count, :recording_count, :session_count, :biography, :favorite_count, :audio_latency, :upcoming_session_count, :age, :website, :skill_level, :reuse_card, :purchased_jamtracks_count if @user.musician? node :location do @user.location end @@ -17,6 +17,10 @@ if @user == current_user geoiplocation.info if geoiplocation end + node :free_jamtrack do |user| + Rails.application.config.one_free_jamtrack_per_user && user.has_redeemable_jamtrack + end + node :mods do |user| user.mods_json end diff --git a/web/app/views/clients/_account.html.erb b/web/app/views/clients/_account.html.erb index d675679a5..a630ef918 100644 --- a/web/app/views/clients/_account.html.erb +++ b/web/app/views/clients/_account.html.erb @@ -94,18 +94,19 @@
    -

    subscriptions:

    +

    jamtracks:

    - - N/A
    - This feature not yet implemented + + {{data.licenseDetail}} + +
    + JamTracks License
    - + UPDATE

    @@ -117,14 +118,11 @@
    - - N/A
    - This feature not yet implemented + View Payment History
    - + VIEW

    diff --git a/web/app/views/clients/_account_jamtracks.html.slim b/web/app/views/clients/_account_jamtracks.html.slim new file mode 100644 index 000000000..966523768 --- /dev/null +++ b/web/app/views/clients/_account_jamtracks.html.slim @@ -0,0 +1,39 @@ +/! Account jamtracks Dialog +#account-jamtracks.screen.secondary layout='screen' layout-id='account/jamtracks' + .content-head + .content-icon + = image_tag "content/icon_account.png", :width => 27, :height => 20 + h1 my account + = render "screen_navigation" + + /! jamtracks scrolling area + .content-body + .content-body-scroller.account-content-scroller#account-jamtracks-content-scroller + .content-wrapper.account-jamtracks + .jamtracks-header + .left.jamtracks-caption + h2 my jamtracks: + .clearall + + #account-my-jamtracks + table.generaltable + thead + th TITLE + th ORIGINAL ARTIST + th ACTIONS + tbody + .right + a.button-grey href="javascript:history.go(-1)" BACK + +script#template-account-jamtrack type='text/template' + tbody + = "{% _.each(data.jamtracks, function(jamtrack) { %}" + tr data-id="{{jamtrack.id}}" data-genre="{{jamtrack.genre}}" + td + | {{jamtrack.name}} + td + | {{jamtrack.original_artist}} + td + .table-link: a.jamtrack-solo-session href= '#' jamtrack-id="{{jamtrack.id}}" Get into solo session + .table-link: a.jamtrack-group-session href= '#' jamtrack-id="{{jamtrack.id}}" Get into session others can join + = "{% }); %}" diff --git a/web/app/views/clients/_account_profile_samples.html.erb b/web/app/views/clients/_account_profile_samples.html.erb index 959266f59..79e63f5ae 100644 --- a/web/app/views/clients/_account_profile_samples.html.erb +++ b/web/app/views/clients/_account_profile_samples.html.erb @@ -19,25 +19,25 @@
    - +
    - +
    - +
    - +
    @@ -46,25 +46,25 @@
    - +
    - +
    - +
    - +
    @@ -92,10 +92,9 @@
    - -
    -
    - ADD + + ADD +
    @@ -110,10 +109,9 @@
    - -
    -
    - ADD + + ADD +
    @@ -155,36 +153,58 @@ initialized = true; setTimeout(function() { - window.urlValidator = new JK.SiteValidator('url'); + window.urlValidator = new JK.SiteValidator('url', userNameSuccessCallback, userNameFailCallback); urlValidator.init(); - window.soundCloudValidator = new JK.SiteValidator('soundcloud'); + window.soundCloudValidator = new JK.SiteValidator('soundcloud', userNameSuccessCallback, userNameFailCallback); soundCloudValidator.init(); - window.reverbNationValidator = new JK.SiteValidator('reverbnation'); + window.reverbNationValidator = new JK.SiteValidator('reverbnation', userNameSuccessCallback, userNameFailCallback); reverbNationValidator.init(); - window.bandCampValidator = new JK.SiteValidator('bandcamp'); + window.bandCampValidator = new JK.SiteValidator('bandcamp', userNameSuccessCallback, userNameFailCallback); bandCampValidator.init(); - window.fandalismValidator = new JK.SiteValidator('fandalism'); + window.fandalismValidator = new JK.SiteValidator('fandalism', userNameSuccessCallback, userNameFailCallback); fandalismValidator.init(); - window.youTubeValidator = new JK.SiteValidator('youtube'); + window.youTubeValidator = new JK.SiteValidator('youtube', userNameSuccessCallback, userNameFailCallback); youTubeValidator.init(); - window.facebookValidator = new JK.SiteValidator('facebook'); + window.facebookValidator = new JK.SiteValidator('facebook', userNameSuccessCallback, userNameFailCallback); facebookValidator.init(); - window.twitterValidator = new JK.SiteValidator('twitter'); + window.twitterValidator = new JK.SiteValidator('twitter', userNameSuccessCallback, userNameFailCallback); twitterValidator.init(); - window.soundCloudRecordingValidator = new JK.SiteValidator('rec_soundcloud'); + window.soundCloudRecordingValidator = new JK.SiteValidator('rec_soundcloud', userNameSuccessCallback, userNameFailCallback); soundCloudRecordingValidator.init(); - window.youTubeRecordingValidator = new JK.SiteValidator('rec_youtube'); + window.youTubeRecordingValidator = new JK.SiteValidator('rec_youtube', userNameSuccessCallback, userNameFailCallback); youTubeRecordingValidator.init(); - }, 1) + }, 1); + + function userNameSuccessCallback($inputDiv) { + $inputDiv.removeClass('error'); + $inputDiv.find('.error-text').remove(); + } + + function userNameFailCallback($inputDiv) { + $inputDiv.addClass('error'); + $inputDiv.find('.error-text').remove(); + $inputDiv.append("Invalid username").show(); + } + + function siteSuccessCallback($inputDiv) { + $inputDiv.removeClass('error'); + $inputDiv.find('.error-text').remove(); + } + + function siteFailCallback($inputDiv) { + $inputDiv.addClass('error'); + $inputDiv.find('.error-text').remove(); + $inputDiv.append("Invalid URL").show(); + } }); }); diff --git a/web/app/views/clients/_checkout_order.html.slim b/web/app/views/clients/_checkout_order.html.slim new file mode 100644 index 000000000..76db4d7ff --- /dev/null +++ b/web/app/views/clients/_checkout_order.html.slim @@ -0,0 +1,157 @@ +div layout="screen" layout-id="checkoutOrder" id="checkoutOrderScreen" class="screen secondary" + .content + .content-head + .content-icon= image_tag("content/icon_shopping_cart.png", {:height => 19, :width => 19}) + h1 check out + = render "screen_navigation" + .content-body + .content-body-scroller + .content-wrapper + .checkout-navigation-bar + .order-panel + .payment-wrapper + p.order-prompt.hidden + | Please review your order, and if everything looks correct, click the PLACE YOUR ORDER button. Thank you! + p.empty-cart-prompt.hidden + | You have nothing in your cart. You can go browse for JamTracks  + a href="/client#/jamtrack" here + | . + p.no-account-info-prompt.hidden + | You have no billing info. Please go back to the  + a href="/client#/checkoutPayment" Payment + |  page to enter your billing info. + .order-content + + #order_error.error.hidden + .clearall + .action-bar + + .right + a.button-grey href="#" id="checkout-info-help" HELP + a.button-grey.back href="#" BACK + a.button-orange.place-order href="#" PLACE YOUR ORDER + .clearall + .thanks-panel + h2 Thank you for your order! + br + .thanks-detail We'll send you an email confirming your order shortly. + br + .thanks-detail.jam-tracks-in-browser.hidden + | To play your purchased JamTrack, launch the JamKazam application and open the JamTrack while in a session. + .thanks-detail.purchased-jam-track.hidden + h2.purchased-jam-track-header Downloading Your Purchased JamTracks + span Each JamTrack will be downloaded sequentially. + br + span.notice Note that you do not have to wait for this to complete in order to use your JamTrack later. + br.clear + ul.purchased-list + + + +script type='text/template' id='template-order-content' + .order-left-page + .payment-info-page + h2 ADDRESS & PAYMENT + .address-info + .billing-address + .billing-caption + | BILLING ADDRESS: + a.change-payment-info href="#" change + .clearall + .billing-info-item= "{{data.billing_info.address1}}" + .billing-info-item= "{{data.billing_info.address2}}" + .billing-info-item + | {{data.billing_info.city}}, {{data.billing_info.state}} {{data.billing_info.zip}} + span.country= "{{data.billing_info.country}}" + .shipping-address + .billing-caption + | SHIPPING ADDRESS: + a.change-payment-info href="#" change + .clearall + = "{% if (data.shipping_as_billing) { %}" + .billing-info-item Same as billing address + = "{% } else { %}" + .billing-info-item= "{{data.shipping_info.address1}}" + .billing-info-item= "{{data.shipping_info.address2}}" + .billing-info-item + | {{data.shipping_info.city}}, {{data.shipping_info.state}} {{data.shipping_info.zip}} + span.country= "{{data.shipping_info.country}}" + = "{% } %}" + br + .payment-method-info + .billing-caption + | PAYMENT METHOD: + a.change-payment-info href="#" change + .clearall + + /= image_tag '' + ="Credit card ending {{data.billing_info.last_four}}" + + .clearall + .order-items-page + h2 ORDER DETAILS + .cart-items + .cart-item-caption + span YOUR ORDER INCLUDES: + .cart-item-price + span PRICE + .cart-item-quantity + span QUANTITY + .clearall + = "{% if (data.carts.length == 0) { %}" + .no-cart-items You have no orders now. + = "{% } %}" + = "{% _.each(data.carts, function(cart) { %}" + .cart-item cart-id="{{cart.id}}" + .cart-item-caption + = "{{cart.cart_type}}: {{cart.product_info.name}}" + .cart-item-price + = "$ {{Number(cart.product_info.total_price).toFixed(2)}}" + .cart-item-quantity + = "{{cart.quantity}}" + .clearall + = "{% }); %}" + .clearall + .order-right-page + h2 PLACE ORDER + .recurly-data.hidden + = "{% _.each(data.carts, function(cart) { %}" + .plan data-plan-code="{{cart.product_info.plan_code}}" + input data-recurly="plan" type="text" value="{{cart.product_info.plan_code}}" + = "{% }); %}" + .order-summary + .place-order-center + a.button-orange.place-order href="#" PLACE YOUR ORDER + .clearall + + .billing-caption ORDER SUMMARY: + .order-items-header.order-total Order items: + .order-items-value.order-total= "{{data.sub_total}}" + .clearall + .order-items-header.shipping-handling Shipping & handling: + .order-items-value.shipping-handling= "{{data.shipping_handling}}" + .clearall + .line + .order-items-header.sub-total Total before tax: + .order-items-value.sub-total= "{{data.sub_total}}" + .clearall + .order-items-header.taxes Taxes: + .order-items-value.taxes= "{{data.taxes}}" + .clearall + .line + .order-items-header.grand-total Order total: + .order-items-value.grand-total= "{{data.grand_total}}" + .clearall + .order-help + span By placing your order, you agree to JamKazam's + ' + a href="http://www.jamkazam.com/corp/terms" rel="external" terms of service + ' + span and + ' + a href="http://www.jamkazam.com/corp/returns" rel="external" returns policy + span . + .clearall + +script type='text/template' id='template-purchased-jam-track' + li data-jam-track-id="{{data.jam_track_id}}" \ No newline at end of file diff --git a/web/app/views/clients/_checkout_payment.html.slim b/web/app/views/clients/_checkout_payment.html.slim new file mode 100644 index 000000000..e937ef434 --- /dev/null +++ b/web/app/views/clients/_checkout_payment.html.slim @@ -0,0 +1,214 @@ +div layout="screen" layout-id="checkoutPayment" id="checkoutPaymentScreen" class="screen secondary no-login-required" + .content + .content-head + .content-icon= image_tag("content/icon_shopping_cart.png", {:height => 19, :width => 19}) + h1 check out + = render "screen_navigation" + .content-body + .content-body-scroller + .content-wrapper + .checkout-navigation-bar + .payment-wrapper + p.payment-prompt.free-jamtrack.hidden + | Please enter your billing address and payment information below. You will not be billed for your first JamTrack, which is 100% free.  + | But we need this data to prevent fraud/abuse of those who would create multiple accounts to collect multiple free JamTracks.  + | You will not be billed for any charges of any kind without your explicit authorization.  + | There are no "hidden" charges or fees, thank you! + p.payment-prompt.no-free-jamtrack.hidden + | Please enter your billing address and payment information below.  + + form class="payment-info" id="checkout-payment-info" + .row.first + .left-side + .billing-address + h2.billing-caption BILLING ADDRESS + #divBillingFirstName.field + .billing-label + label for="billing-first-name" First Name: * + .billing-value + input type="text" id="billing-first-name" + .clearall + #divBillingLastName.field + .billing-label + label for="billing-last-name" Last Name: * + .billing-value + input type="text" id="billing-last-name" + .clearall + #divBillingAddress1.field + .billing-label + label for="billing-address1" Address 1: * + .billing-value + input type="text" id="billing-address1" + .clearall + #divBillingAddress2.field + .billing-label + label for="billing-address2" Address 2: + .billing-value + input type="text" id="billing-address2" + .clearall + #divBillingCity.field + .billing-label + label for="billing-city" City: * + .billing-value + input type="text" id="billing-city" + .clearall + #divBillingState.field + .billing-label + label for="billing-state" State/Region: * + .billing-value + input type="text" id="billing-state" + .clearall + #divBillingZip.field + .billing-label + label for="billing-zip" Zip: * + .billing-value + input type="text" id="billing-zip" + .clearall + #divBillingCountry.field + .billing-label + label for="billing-country" Country: * + .billing-value + select id="billing-country" + option value="US" US + .clearall + .right-side + .payment-method + h2.payment-method-caption PAYMENT METHOD + .new-card-info + #divCardName.field.hidden + .card-label + label for="card-name" Name of Card: * + .card-value + input type="text" id="card-name" + .clearall + #divCardNumber.field + .card-label + label for="card-number" Card Number: * + .card-value + input type="text" id="card-number" + .clearall + #divCardExpiry.field + .card-label Expiration Date: * + .card-value + =date_select("card", "expire-date", use_two_digit_numbers: true, discard_day: true, :start_year => Time.now.year, :end_year => Time.now.year + 18, :order => [:month, :day, :year], :default => -25.years.from_now, :html => {:class => "account-profile-birthdate", :id => "card-expiry"}) + .clearall + #divCardVerify.field + .card-label + label for="card-verify" + | CVV Code: * + .hint.cvv + | (back of card) + .card-value + input type="text" id="card-verify" + .clearall + .reuse-existing-card + .card-label + .card-value + .reuse-existing-card-checkbox.ichecbuttons + input type="checkbox" id="reuse-existing-card" name="reuse-existing-card" checked="checked" + .reuse-existing-card-helper + label for="reuse-existing-card" + | Use card ending in  + span.existing-card-ends-with + .clearall + .card-label + .card-value + .save-card-checkbox.ichecbuttons + input type="checkbox" id="save-card" name="save-card" checked="checked" + .divSaveCardHelper + label for="save-card" Save card for future use + .clearall + .clearall + .clearall + .row.second + left-side.hidden + .shipping-address + h2.shipping-address-label SHIPPING ADDRESS + .shipping-as-billing.ichecbuttons + input type="checkbox" id="shipping-as-billing" name="shipping-as-billing" checked="checked" + .divBillingHelper + label for="shipping-as-billing" Same as billing address + .clearall + .shipping-address-detail.hidden + #divShippingFirstName.field + .shipping-label + label for="shipping-first-name" First Name: + .shipping-value + input type="text" id="shipping-first-name" + .clearall + #divShippingLastName.field + .shipping-label + label for="shipping-last-name" Last Name: + .shipping-value + input type="text" id="shipping-last-name" + .clearall + #divShippingAddress1.field + .shipping-label + label for="shipping-address1" Address 1: + .shipping-value + input type="text" id="shipping-address1" + .clearall + #divShippingAddress2.field + .shipping-label + label for="shipping-address2" Address 2: + .shipping-value + input type="text" id="shipping-address2" + .clearall + #divShippingCity.field + .shipping-label + label for="shipping-city" City: + .shipping-value + input type="text" id="shipping-city" + .clearall + #divShippingState.field + .shipping-label + label for="shipping-state" State/Region: + .shipping-value + input type="text" id="shipping-state" + .clearall + #divShippingZip.field + .shipping-label + label for="shipping-zip" Zip: + .shipping-value + input type="text" id="shipping-zip" + .clearall + #divShippingCountry.field + .shipping-label + label for="shipping-country" Country: + .shipping-value + input type="text" id="shipping-country" + .clearall + .right-side + .jamkazam-account-signup + h2.jamkazam-account-caption JAMKAZAM ACCOUNT + #divJamKazamEmail.field + .account-label + label for="email" Email Address: * + .account-value + input name="email" id="checkout-signup-email" type="text" + .clearall + #divJamKazamPassword.field + .account-label + label for="password" Password: * + .account-value + input name="password" id="checkout-signup-password" type="password" + .clearall + #divJamKazamTos.field + .terms-of-service.ichecbuttons + input type="checkbox" name="terms-of-service" + .terms-of-service-label-holder + label for="terms-of-service" + | I have read and agree to the JamKazam  + a rel="external" href=corp_terms_path terms of service + .clearall + .clearall + .row.third.hidden#payment_error + + + .clearall + .action-bar + + .right + a href="#" id="payment-info-help" class="button-grey" HELP + a href="#" id="payment-info-next" class="button-orange" NEXT + .clearall diff --git a/web/app/views/clients/_checkout_signin.html.slim b/web/app/views/clients/_checkout_signin.html.slim new file mode 100644 index 000000000..d6dd4ccdb --- /dev/null +++ b/web/app/views/clients/_checkout_signin.html.slim @@ -0,0 +1,92 @@ +div layout="screen" layout-id="checkoutSignin" id="checkoutSignInScreen" class="screen secondary no-login-required" + .content + .content-head + .content-icon= image_tag("content/icon_shopping_cart.png", {:height => 19, :width => 19}) + h1 check out + = render "screen_navigation" + .content-body + .content-body-scroller + .checkout-signin + .checkout-navigation-bar + + .content-holder + .already-signed-in + h3 YOU ARE ALREADY LOGGED IN + p.carry-on-prompt + | You can move on to the next step of checkout. + .actions + a.btnNext.button-orange NEXT + + .left-side + h3 ALREADY A MEMBER OF THE JAMKAZAM COMMUNITY? + .left-side-content + .signin-form + .signin-prompt Sign in using your email address: + .clearall + form.signin-form + .input-elements + .out + label.inline Email: + .email.field + input name='email' autofocus="true" type="text" + .out + label.inline Password: + .password.field + input name='password' autofocus="true" type="password" + .login-error-msg Invalid login + + + br clear='all' + + .actions + small + a.forgot-password href='/request_reset_password' Forgot Password? + = link_to "SIGN IN", '#', class: 'button-orange signin-submit' + + + p.facebook-prompt Or sign in using Facebook: + = link_to image_tag("content/button_facebook_signin.png", {:width => 249, :height => 46}), '/auth/facebook', class: "signin-facebook" + .right-side + h3 NOT A MEMBER YET? + + p.signup-later-prompt + | Thousands of musicians are now registered members of JamKazam. Click the NEXT button below to join us, and welcome! + + .actions + a.btnNext.button-orange NEXT + +script type='text/template' id='template-checkout-navigation' + .checkout-navigation + .nav-signin + = "{% if (data.current == 1) { %}" + .nav-text.selected + .badge-number 1 + | Sign In + = "{% } else { %}" + .nav-text + .badge-number.disabled 1 + | Sign In + = "{% } %}" + .clearall + .nav-payment-info + = "{% if (data.current == 2) { %}" + .nav-text.selected + .badge-number 2 + | Address & Payment + = "{% } else { %}" + .nav-text + .badge-number.disabled 2 + | Address & Payment + = "{% } %}" + .clearall + .nav-place-order + = "{% if (data.current == 3) { %}" + .nav-text.selected + .badge-number 3 + | Place Order + = "{% } else { %}" + .nav-text + .badge-number.disabled 3 + | Place Order + = "{% } %}" + .clearall diff --git a/web/app/views/clients/_flash.html.slim b/web/app/views/clients/_flash.html.slim new file mode 100644 index 000000000..ec6b293ce --- /dev/null +++ b/web/app/views/clients/_flash.html.slim @@ -0,0 +1,3 @@ +script type="text/template" id="template-flash-notice" + .flash-notice + .flash-content \ No newline at end of file diff --git a/web/app/views/clients/_header.html.erb b/web/app/views/clients/_header.html.erb index 855f0e988..de9015e66 100644 --- a/web/app/views/clients/_header.html.erb +++ b/web/app/views/clients/_header.html.erb @@ -13,8 +13,8 @@ <% if Rails.application.config.jam_tracks_available %> - - + <% end %> diff --git a/web/app/views/clients/_home.html.slim b/web/app/views/clients/_home.html.slim index fb6347c84..f904459bb 100644 --- a/web/app/views/clients/_home.html.slim +++ b/web/app/views/clients/_home.html.slim @@ -1,6 +1,6 @@ -.screen layout="screen" layout-id="home" +.screen.no-login-required layout="screen" layout-id="home" / Layout is different if jam_tracks tile available: - -jamtracks=Rails.configuration.jam_tracks_available + -jamtracks=Rails.configuration.jam_tracks_available || (current_user && current_user.admin) -if (jamtracks) -small_tile_size="2.4" -column_positions=["0.0,1", "2.4,1", "4.8,1", "7.2,1", "9.6,1"] @@ -11,23 +11,23 @@ / individual spells span those spaces -if @nativeClient .grid layout-grid="2x12" - .homecard.createsession layout-grid-columns="4" layout-grid-position="0,0" layout-grid-rows="1" layout-link="createSession" type="createSession" + .homecard.createsession layout-grid-columns="4" layout-grid-position="0,0" layout-grid-rows="1" layout-link="createSession" type="createSession" class="#{logged_in_not_logged_in_class}" h2 create session .homebox-info /! 4 friends online, 2 currently in sessions - .homecard.findsession layout-grid-columns="4" layout-grid-position="4,0" layout-grid-rows="1" layout-link="findSession" type="findSession" + .homecard.findsession layout-grid-columns="4" layout-grid-position="4,0" layout-grid-rows="1" layout-link="findSession" type="findSession" class="#{logged_in_not_logged_in_class}" h2 find session .homebox-info /! 1 session invitation, 19 public sessions active - .homecard.feed layout-grid-columns="4" layout-grid-position="8,0" layout-grid-rows="1" layout-link="feed" + .homecard.feed layout-grid-columns="4" layout-grid-position="8,0" layout-grid-rows="1" layout-link="feed" class="#{logged_in_not_logged_in_class}" h2 feed .homebox-info /! 4 friends online, 2 currently in sessions - .homecard.musicians layout-grid-columns=small_tile_size layout-grid-position=column_positions[0] layout-grid-rows="1" layout-link="musicians" + .homecard.musicians layout-grid-columns=small_tile_size layout-grid-position=column_positions[0] layout-grid-rows="1" layout-link="musicians" class="#{logged_in_not_logged_in_class}" h2 musicians .homebox-info /! 5 followers, 3 following - .homecard.bands layout-grid-columns=small_tile_size layout-grid-position=column_positions[1] layout-grid-rows="1" layout-link="bands" + .homecard.bands layout-grid-columns=small_tile_size layout-grid-position=column_positions[1] layout-grid-rows="1" layout-link="bands" class="#{logged_in_not_logged_in_class}" h2 bands .homebox-info /! 1 session invitation, 19 public sessions active @@ -36,46 +36,46 @@ h2 jamtracks .homebox-info /! 5 followers, 3 following - .homecard.profile layout-grid-columns=small_tile_size layout-grid-position=column_positions[3] layout-grid-rows="1" + .homecard.profile layout-grid-columns=small_tile_size layout-grid-position=column_positions[3] layout-grid-rows="1" class="#{logged_in_not_logged_in_class}" h2 profile .homebox-info /! 5 followers, 3 following - .homecard.account layout-grid-columns=small_tile_size layout-grid-position=column_positions[4] layout-grid-rows="1" layout-link="account" + .homecard.account layout-grid-columns=small_tile_size layout-grid-position=column_positions[4] layout-grid-rows="1" layout-link="account" class="#{logged_in_not_logged_in_class}" h2 account .homebox-info /! free service level -else .grid layout-grid="2x12" - .homecard.createsession layout-grid-columns="4" layout-grid-position="0,0" layout-grid-rows="1" layout-link="createSession" type="createSession" + .homecard.createsession layout-grid-columns="4" layout-grid-position="0,0" layout-grid-rows="1" layout-link="createSession" type="createSession" class="#{logged_in_not_logged_in_class}" h2 create session .homebox-info /! 4 friends online, 2 currently in sessions - .homecard.findsession layout-grid-columns="4" layout-grid-position="4,0" layout-grid-rows="1" layout-link="findSession" type="findSession" + .homecard.findsession layout-grid-columns="4" layout-grid-position="4,0" layout-grid-rows="1" layout-link="findSession" type="findSession" class="#{logged_in_not_logged_in_class}" h2 find session .homebox-info /! 1 session invitation, 19 public sessions active - .homecard.feed layout-grid-columns="4" layout-grid-position="8,0" layout-grid-rows="1" layout-link="feed" + .homecard.feed layout-grid-columns="4" layout-grid-position="8,0" layout-grid-rows="1" layout-link="feed" class="#{logged_in_not_logged_in_class}" h2 feed .homebox-info /! 4 friends online, 2 currently in sessions - .homecard.musicians layout-grid-columns=small_tile_size layout-grid-position=column_positions[0] layout-grid-rows="1" layout-link="musicians" + .homecard.musicians layout-grid-columns=small_tile_size layout-grid-position=column_positions[0] layout-grid-rows="1" layout-link="musicians" class="#{logged_in_not_logged_in_class}" h2 musicians .homebox-info /! 5 followers, 3 following - .homecard.bands layout-grid-columns=small_tile_size layout-grid-position=column_positions[1] layout-grid-rows="1" layout-link="bands" + .homecard.bands layout-grid-columns=small_tile_size layout-grid-position=column_positions[1] layout-grid-rows="1" layout-link="bands" class="#{logged_in_not_logged_in_class}" h2 bands .homebox-info -if jamtracks /! 1 session invitation, 19 public sessions active - .homecard.jamtrack layout-grid-columns=small_tile_size layout-grid-position=column_positions[2] layout-grid-rows="1" layout-link="jamtrack" + .homecard.jamtrack layout-grid-columns=small_tile_size layout-grid-position=column_positions[2] layout-grid-rows="1" layout-link="jamtrackLanding" h2 jamtracks .homebox-info /! 5 followers, 3 following - .homecard.profile layout-grid-columns=small_tile_size layout-grid-position=column_positions[3] layout-grid-rows="1" + .homecard.profile layout-grid-columns=small_tile_size layout-grid-position=column_positions[3] layout-grid-rows="1" class="#{logged_in_not_logged_in_class}" h2 profile .homebox-info /! 5 followers, 3 following - .homecard.account layout-grid-columns=small_tile_size layout-grid-position=column_positions[4] layout-grid-rows="1" layout-link="account" + .homecard.account layout-grid-columns=small_tile_size layout-grid-position=column_positions[4] layout-grid-rows="1" layout-link="account" class="#{logged_in_not_logged_in_class}" h2 account .homebox-info /! free service level diff --git a/web/app/views/clients/_jamtrack.html.haml b/web/app/views/clients/_jamtrack.html.haml deleted file mode 100644 index ba076ee19..000000000 --- a/web/app/views/clients/_jamtrack.html.haml +++ /dev/null @@ -1,83 +0,0 @@ -%div{ layout: 'screen', :'layout-id' => 'jamtrack', id: 'jamtrackScreen', :class => 'screen secondary'} - .content - .content-head - .content-icon= image_tag("content/icon_jamtracks.png", {:height => 19, :width => 19}) - %h1 jamtracks - %a{href: "#", class: "jamtrack_help"} What is a JamTrack? - = render "screen_navigation" - .content-body - = form_tag('', {:id => 'jamtrack-find-form', :class => 'inner-content'}) do - = render(:partial => "web_filter", :locals => {:search_type => Search::PARAM_JAMTRACK}) - .filter-body - .content-body-scroller - .profile-wrapper - .jamtrack-content - %a{href: "/api/jamtracks?page=1", class: "btn-next-pager"}= 'Next' - %div{id: 'end-of-jamtrack-list', class: 'end-of-list'}= 'No more Jamtracks' - -%script{type: 'text/template', id: 'template-jamtrack'} - .jamtrack-record{"jamtrack-id" => "{{data.jamtrack.id}}"} - .jamtrack-detail - .detail-label - Name: - .detail-value - {{data.jamtrack.name}} - .clearall.detail-label - Type: - .detail-value - {{data.jamtrack.recording_type}} - .clearall.detail-label - Original Artist: - .detail-value - {{data.jamtrack.original_artist}} - .clearall.detail-label - Genre: - .detail-value - {{data.jamtrack.genres[0]}} - .clearall.detail-label - Writer/Composer: - .detail-value - {{[data.jamtrack.songwriter, data.jamtrack.publisher].join(", ")}} - .clearall.detail-label - Copyright: - .copyright-value - = "{% if (data.jamtrack.licensor != null) { %}" - {{data.jamtrack.licensor.name}} - ="{% }; %}" - .detail-arrow - = image_tag 'down_arrow.png', class: 'jamtrack-detail-btn' - .clearall.jamtrack-description - .detail-label - Description - .detail-value - {{data.jamtrack.description}} - .clearall - .jamtrack-tracks - .tracks-caption - Tracks in This Recording: - = "{% _.each(data.jamtrack.tracks, function(track) { %}" - - = "{% if(track.track_type == 'Master') return; %}" - .track-instrument - .instrument-image - %img{src: "{{track.instrument_url}}", width: 24, height: 24} - .instrument-desc - {{track.instrument_desc}} - .clearall - = "{% }); %}" - .jamtrack-action - %a{href: "#", class: 'play-button', "data-jamtrack-id" => "{{data.jamtrack.id}}"} - = image_tag 'shared/play_button.png' - .jamtrack-price - {{"$ " + data.jamtrack.price}} - = "{% if (data.jamtrack.added_cart) { %}" - %a.jamtrack-add-cart-disabled.button-grey.button-disabled{href: "javascript:void(0)"} Purchased - = "{% } else { %}" - %a.jamtrack-add-cart.button-orange{href: "#", "data-jamtrack-id" => "{{data.jamtrack.id}}"} Add to Cart - = "{% }; %}" - = "{% if (data.jamtrack.sales_region == 'United States') { %}" - .jamtrack-license - This JamTrack available only to US customers. - %a{href: "#", class: 'license-us-why'} why? - = "{% }; %}" - .clearall \ No newline at end of file diff --git a/web/app/views/clients/_jamtrack.html.slim b/web/app/views/clients/_jamtrack.html.slim new file mode 100644 index 000000000..79e2cf17e --- /dev/null +++ b/web/app/views/clients/_jamtrack.html.slim @@ -0,0 +1,114 @@ +#jamtrackScreen.screen.secondary.no-login-required layout='screen' layout-id='jamtrack' + .content + .content-head + .content-icon=image_tag("content/icon_jamtracks.png", height:19, width:19 ) + h1 jamtracks + =render "screen_navigation" + .content-body + =form_tag('', {:id => 'jamtrack-find-form', :class => 'inner-content'}) do + =render(:partial => "web_filter", :locals => {:search_type => Search::PARAM_JAMTRACK}) + .filter-body + .content-body-scroller + .profile-wrapper + .jamtrack-content + a.btn-next-pager href="/api/jamtracks?page=1" Next + .end-of-jamtrack-list.end-of-list="No more Jamtracks" + +script#template-jamtrack type='text/template' + .jamtrack-record jamtrack-id="{{data.jamtrack.id}}" + .top_bar + .jamtrack-detail.jamtrack-header JAMTRACK + .jamtrack-tracks.jamtrack-header TRACKS INCLUDED/PREVIEW + .jamtrack-action.jamtrack-header SHOP + .jamtrack-detail + .detail-label + | Title: + .detail-value + | {{data.jamtrack.name}} + / .clearall.detail-label + / | Type: + / .detail-value + / | {{data.jamtrack.recording_type}} + / .clearall.detail-label + / | Original Artist: + / .detail-value + / | {{data.jamtrack.original_artist}} + .clearall.detail-label + | Original Artist: + .detail-value + | {{data.jamtrack.original_artist}} + .clearall.detail-label + | Genre: + .detail-value + | {{data.jamtrack.genres[0]}} + ="{% if (data.expanded) { %}" + .clearall.detail-label + | Writer(s): + .detail-value + | {{data.jamtrack.songwriter}} + .clearall.detail-label + | Publisher: + .detail-value + | {{data.jamtrack.publisher}} + .clearall.detail-label + | Description: + .detail-value + | {{data.jamtrack.description}} + ="{% } %}" + + + / / / .clearall.detail-label + / | Copyright: + / .copyright-value + / ="{% if (data.jamtrack.licensor !=null) { %}" + / | {{data.jamtrack.licensor.name}} + / ="{% }; %}" + / .clearall.jamtrack-description + / .detail-label + / | Description + / .detail-value + / | {{data.jamtrack.description}} + / .clearall + .jamtrack-tracks + / .tracks-caption + / | Tracks in This Recording: + ="{% counter = 0 %}" + ="{% _.each(data.jamtrack.tracks, function(track) { %}" + ="{% if(track.track_type == JK.MASTER_TRACK) return; %}" + .track-instrument href="{{track.url_44}}" + .instrument-image + img src="/assets/shared/play_button.png" width=24 height=24 + .instrument-image + img src="{{track.instrument_url}}" width=24 height=24 + .instrument-desc + | {{track.instrument_desc}} + .clearall + ="{% }); %}" + .detail-arrow + .jamtrack-detail-btn + ="{% if (data.expanded) { %}" + | hide tracks + =image_tag("up_arrow.png") + ="{% } else { %}" + | preview all tracks + =image_tag("down_arrow.png") + ="{% } %}" + + .jamtrack-action + / a.play-button href="#" data-jamtrack-id="{{data.jamtrack.id}}" + / =image_tag "shared/play_button.png" + .jamtrack-price + | {{"$ " + data.jamtrack.price}} + ="{% if (data.jamtrack.purchased) { %}" + a.jamtrack-add-cart-disabled.button-grey.button-disabled href="javascript:void(0)" Purchased + ="{% } else if (data.jamtrack.added_cart) { %}" + a.jamtrack-add-cart-disabled.button-grey.button-disabled href="client#/shoppingCart" Already In Cart + ="{% } else { %}" + a.jamtrack-add-cart.button-orange href="#" data-jamtrack-id="{{data.jamtrack.id}}" Add to Cart + ="{% }; %}" + ="{% if (data.jamtrack.sales_region==JK.AVAILABILITY_US) { %}" + .jamtrack-license + | This JamTrack available only to US customers. + a.license-us-why href="#", why? + ="{% }; %}" + .clearall diff --git a/web/app/views/clients/_jamtrack_landing.html.slim b/web/app/views/clients/_jamtrack_landing.html.slim new file mode 100644 index 000000000..853ee05fe --- /dev/null +++ b/web/app/views/clients/_jamtrack_landing.html.slim @@ -0,0 +1,35 @@ +#jamtrackLanding.screen.secondary layout='screen' layout-id='jamtrackLanding' + .content + .content-head + .content-icon=image_tag("content/icon_jamtracks.png", height:19, width:19) + h1 jamtracks + = render "screen_navigation" + .content-body + .list-columns + .about + h2 what are jamtracks? + p.what + .details JamTracks are the best way to play along with your favorite music! Unlike traditional backing tracks, JamTracks are professionally mastered, complete multitrack recordings, with fully isolated tracks for each and every part of the master mix. Used with the free JamKazam app & Internet service, you can: + ul + li Solo just the part you want to play in order to hear and learn it + li Mute just the part you want to play and play along with the rest + li Make audio recordings and share them via Facebook or URL + li Make video recordings and share them via YouTube + li And even go online to play with others in real time -- for example, you can play the electric guitar lead, while someone else plays the bass, and all other parts play from the recorded tracks in your session + / TODO: put in video thumbnail when available: + .browse + h2 my jamtracks + p.howto + .details + span="To play with your JamTracks, open a JamTrack while in a session in the JamKazam app. Or " + a href="client#/jamtrack" visit the JamTracks Section of your account. + .free-jamtrack.orange-fill + | For a limited time, get one JamTrack free. Browse JamTracks below, add one to your shopping cart, and we'll make it free during the checkout process. + h2 browse jamtracks + .browse-header + span="browse by band " + a href="client#/jamtrack" or browse all jamtracks + .band-browse + ul#band_list + li#no_bands_found.hidden No bands found + diff --git a/web/app/views/clients/_order.html.slim b/web/app/views/clients/_order.html.slim index ffab91d7a..f5ea0cc91 100644 --- a/web/app/views/clients/_order.html.slim +++ b/web/app/views/clients/_order.html.slim @@ -71,19 +71,20 @@ div layout="screen" layout-id="order" id="orderScreen" class="screen secondary" input type="text" id="card-name" .clearall #divCardNumber - .card-label + .card-label.mt10 label for="card-number" Card Number: - .card-value + .card-value.mt10 input type="text" id="card-number" .clearall - .card-label Expiration Date: - .card-value - = date_select("card", "expire-date", use_two_digit_numbers: true, discard_day: true, :start_year => Time.now.year, :end_year => Time.now.year + 18, :order => [:month, :day, :year], :default => -25.years.from_now, :html=>{:class => "account-profile-birthdate"} ) - .clearall + #divCardExpiry + .card-label.mt10 Expiration Date: + .card-value.mt10 + =date_select("card", "expire-date", use_two_digit_numbers: true, discard_day: true, :start_year => Time.now.year, :end_year => Time.now.year + 18, :order => [:month, :day, :year], :default => -25.years.from_now, :html=>{:class => "account-profile-birthdate", :id=>"card-expiry"} ) + .clearall #divCardVerify - .card-label + .card-label.mt10 label for="card-verify" Verification Value: - .card-value + .card-value.mt10 input type="text" id="card-verify" .clearall .card-label.mt15 diff --git a/web/app/views/clients/_session.html.slim b/web/app/views/clients/_session.html.slim index eb7541100..ae422b6b7 100644 --- a/web/app/views/clients/_session.html.slim +++ b/web/app/views/clients/_session.html.slim @@ -46,61 +46,63 @@ | CHAT .voicechat-gain .voicechat-mute.enabled[control="mute" mixer-id=""] - .session-livetracks - h2 - | live tracks - .session-add[layout-link="select-invites"] - a#session-invite-musicians[href="#"] - = image_tag "content/icon_add.png", {:width => 19, :height => 19, :align => "texttop"} - |   Invite Musicians - .session-tracks-scroller - #session-livetracks-container - .when-empty.livetracks - | No other musicians - br - | are in your session - br[clear="all"] - #recording-start-stop.recording - a - = image_tag "content/recordbutton-off.png", {:width => 20, :height => 20, :align => "absmiddle"} - |    - span#recording-status - | Make a Recording - .session-recordings - h2 - | other audio - .session-recording-name-wrapper - .session-recording-name.left - | (No audio loaded) - .session-add.right - a#close-playback-recording[href="#"] - = image_tag "content/icon_close.png", {:width => 18, :height => 20, :align => "texttop"} - |   Close - .session-tracks-scroller - #session-recordedtracks-container - .when-empty.recordings - span.open-media-file-header - = image_tag "content/icon_folder.png", {width:22, height:20} - | Open: - ul.open-media-file-options - li - a#open-a-recording[href="#"] - | Recording - - if Rails.application.config.jam_tracks_available + .session-fluidtracks + .session-livetracks + h2 + | live tracks + .session-add[layout-link="select-invites"] + a#session-invite-musicians[href="#"] + = image_tag "content/icon_add.png", {:width => 19, :height => 19, :align => "texttop"} + |   Invite Musicians + .session-tracks-scroller + #session-livetracks-container + .when-empty.livetracks + | No other musicians + br + | are in your session + br[clear="all"] + #recording-start-stop.recording + a + = image_tag "content/recordbutton-off.png", {:width => 20, :height => 20, :align => "absmiddle"} + |    + span#recording-status + | Make a Recording + .session-recordings + h2 + | other audio + .session-recording-name-wrapper + .session-recording-name.left + | (No audio loaded) + .session-add.right + a#close-playback-recording[href="#"] + = image_tag "content/icon_close.png", {:width => 18, :height => 20, :align => "texttop"} + |   Close + .session-tracks-scroller + #session-recordedtracks-container + .when-empty.recordings + span.open-media-file-header + = image_tag "content/icon_folder.png", {width:22, height:20} + | Open: + ul.open-media-file-options li - a#open-a-jamtrack[href="#"] - | JamTrack - - if Rails.application.config.backing_tracks_available - li - a#open-a-backingtrack[href="#"] - | Audio File - .when-empty.use-metronome-header - - if Rails.application.config.metronome_available - = image_tag "content/icon_metronome.png", {width:22, height:20} - a#open-a-metronome[href="#"] - | Use Metronome - br[clear="all"] - = render "play_controls" + a#open-a-recording[href="#"] + | Recording + - if Rails.application.config.jam_tracks_available || (current_user && current_user.admin) + li + a#open-a-jamtrack[href="#"] + | JamTrack + - if Rails.application.config.backing_tracks_available + li + a#open-a-backingtrack[href="#"] + | Audio File + .when-empty.use-metronome-header + - if Rails.application.config.metronome_available + = image_tag "content/icon_metronome.png", {width:22, height:20} + a#open-a-metronome[href="#"] + | Use Metronome + br[clear="all"] + .play-controls-holder + = render "play_controls" = render "configureTrack" = render "addTrack" = render "addNewGear" diff --git a/web/app/views/clients/_shopping_cart.html.haml b/web/app/views/clients/_shopping_cart.html.haml index df3be13f9..36fb6221e 100644 --- a/web/app/views/clients/_shopping_cart.html.haml +++ b/web/app/views/clients/_shopping_cart.html.haml @@ -1,4 +1,4 @@ -%div{ layout: 'screen', :'layout-id' => 'shoppingCart', id: 'shoppingCartScreen', :class => 'screen secondary'} +%div{ layout: 'screen', :'layout-id' => 'shoppingCart', id: 'shoppingCartScreen', :class => 'screen secondary no-login-required'} .content .content-head .content-icon= image_tag("content/icon_shopping_cart.png", {:height => 19, :width => 19}) @@ -19,35 +19,37 @@ .clearall %script{type: 'text/template', id: 'template-shopping-cart-body'} - .cart-items - .cart-item-caption#header - Your shopping cart now contains: - .cart-item-price - %span{style: "text-decoration: underline;"} Price - .cart-item-quantity - %span{style: "text-decoration: underline;"} Quantity - .clearall + %table.cart-items + %tr + %th.cart-item-caption + YOUR SHOPPING CART NOW CONTAINS: + %th.cart-item-price + Price + %th.cart-item-quantity + Quantity + %th = "{% if (data.carts.length == 0) { %}" - .no-cart-items Nothing in cart + %tr + %td.no-cart-items colspan=4 Nothing in cart = "{% } %}" + = "{% _.each(data.carts, function(cart) { %}" - .cart-item{"cart-id" => "{{cart.id}}"} - .cart-item-caption + %tr.cart-item{"cart-id" => "{{cart.id}}"} + %td.cart-item-caption {{cart.cart_type}}: {{cart.product_info.name}} - .cart-item-price + %td.cart-item-price $ {{cart.product_info.price}} - .cart-item-quantity + %td.cart-item-quantity {{cart.quantity}} - .cart-item-actions - %a.button-grey.remove-cart{href: "#", "cart-id" => "{{cart.id}}"} DELETE - .clearall + %td.cart-item-actions + %a.button-grey.remove-cart{href: "#", "cart-id" => "{{cart.id}}"} + DELETE = "{% }); %}" .shopping-sub-total Subtotal: $ {{data.sub_total}} .clearall - .left - %a.button-grey{href: "#"} HELP .right + %a.button-grey{href: "#"} HELP %a.button-orange{href: "/client#/jamtrack"} CONTINUE SHOPPING %a.button-orange.proceed-checkout{href: "#"} PROCEED TO CHECKOUT .clearall \ No newline at end of file diff --git a/web/app/views/clients/_signin.html.slim b/web/app/views/clients/_signin.html.slim deleted file mode 100644 index 0310decb1..000000000 --- a/web/app/views/clients/_signin.html.slim +++ /dev/null @@ -1,68 +0,0 @@ -div layout="screen" layout-id="signin" id="signInScreen" class="screen secondary signin-common" - .content - .content-head - .content-icon= image_tag("content/icon_shopping_cart.png", {:height => 19, :width => 19}) - h1 check out - = render "screen_navigation" - .content-body - .checkout-signin - .checkout-navigation-bar - - .signin-form - = link_to image_tag("content/button_facebook_signin.png", {:width => 249, :height => 46}), '/auth/facebook', class: "signin-facebook" - - br - br - br - strong.white Or sign in with JamKazam Account - br - br - - = form_for(:session, url: signin_path('redirect-to' => '/client#/payment_config') + (request.query_string.blank? ? '' : '?' + request.query_string), html: {class:"signin-form #{'login-error' if @login_error}"}) do |f| - .email - = f.label :email, "Email Address:", class: 'inline' - = f.text_field :email, autofocus: true - - .password - = f.label :password, "Password:", class: 'inline' - = f.password_field :password, autofocus: true, class: 'signin-password' - .login-error-msg Invalid login - - br clear='all' - - .actions align='center' - = link_to "SIGN IN", '#', class: 'button-orange signin-submit' - | or - ' - a.show-signup-dialog href='#' Sign Up - br - br - small - a.forgot-password href='/request_reset_password' Forgot Password? - - -script type='text/template' id='template-checkout-navigation' - .checkout-navigation - .nav-signin - = "{% if (data.current == 1) { %}" - .nav-text.selected Sign In - = "{% } else { %}" - .nav-text Sign In - = "{% } %}" - .nav-arrow -> - .clearall - .nav-payment-info - = "{% if (data.current == 2) { %}" - .nav-text.selected Address & Payment - = "{% } else { %}" - .nav-text Address & Payment - = "{% } %}" - .nav-arrow -> - .clearall - .nav-signin - = "{% if (data.current == 3) { %}" - .nav-text.selected Place Order - = "{% } else { %}" - .nav-text Place Order - = "{% } %}" - .clearall diff --git a/web/app/views/clients/_web_filter.html.haml b/web/app/views/clients/_web_filter.html.haml index 1c64bfd59..45efa625a 100644 --- a/web/app/views/clients/_web_filter.html.haml +++ b/web/app/views/clients/_web_filter.html.haml @@ -11,41 +11,31 @@ =content_tag(:div, :id => defined?(id) ? id : 'session-controls', :class => "#{filter_label}-filter filter-head") do =content_tag(:div, :class => "filter-element wrapper") do -if :feed==filter_label - / @begin sort filter =content_tag(:div, 'Sort Feed by:', :class => 'filter-element desc') - =select_tag("#{filter_label}_order_by", options_for_select(Search::F_SORT_OPTS), {:class => "#{filter_label}-order-by easydropdown" } ) - / @end sort filter + =select_tag("#{filter_label}_order_by", options_for_select(Search::F_SORT_OPTS), {:class => "#{filter_label}-order-by easydropdown" } ) -elsif :jamtrack !=filter_label - / @begin order by filter =content_tag(:div, 'Order By:', :class => 'filter-element desc') - =select_tag("#{filter_label}_order_by", options_for_select(Search::M_ORDERINGS), {:class => "#{filter_label}-order-by easydropdown"} ) - / @end order by filter + =select_tag("#{filter_label}_order_by", options_for_select(Search::M_ORDERINGS), {:class => "#{filter_label}-order-by easydropdown"} ) + =content_tag(:div, :class => 'filter-element wrapper') do + / -if (:band==filter_label || :jamtrack==filter_label) + / =content_tag(:div, 'Genre:', :class => 'filter-element desc') + / =select_tag("#{filter_label}_genre", | + / options_for_select([['Any', '']].concat(JamRuby::Genre.all.collect { |ii| [ii.description, ii.id] })), {:class => 'easydropdown'}) | + -if :jamtrack==filter_label + =content_tag(:div, 'Filter JamTracks:', :class => 'filter-element desc') + =select_tag("#{filter_label}_artist", options_for_select([['Any Band', '']].concat(JamTrack.all_artists.collect { |ii| [ii, ii] })), {:class => 'easydropdown'}) =content_tag(:div, :class => 'filter-element wrapper') do - -if :band==filter_label || :jamtrack==filter_label - / @begin genre filter - =content_tag(:div, 'Genre:', :class => 'filter-element desc') - =select_tag("#{filter_label}_genre", | - options_for_select([['Any', '']].concat(JamRuby::Genre.all.collect { |ii| [ii.description, ii.id] })), {:class => 'easydropdown'}) | - / @end genre filter -if :musician==filter_label || :jamtrack==filter_label - / @begin instrument filter - =content_tag(:div, 'Instrument:', :class => 'filter-element desc instrument-selector') - =select_tag("#{filter_label}_instrument", | - options_for_select([['Any', '']].concat(JamRuby::Instrument.all.collect { |ii| [ii.description, ii.id] })), {:class=> "easydropdown"}) | - / @end instrument filter + / =content_tag(:div, 'Instrument:', :class => 'filter-element desc instrument-selector') + =select_tag("#{filter_label}_instrument", options_for_select([['Any Instrument', '']].concat(JamRuby::Instrument.all.collect { |ii| [ii.description, ii.id] })), {:class=> "easydropdown"}) | -if :feed==filter_label - / @begin date filter - =content_tag(:div, 'Include Dates:', :class => 'filter-element desc') - =select_tag("#{filter_label}_date", options_for_select(Search::DATE_OPTS), {:class => "easydropdown"}) - / @end date filter + / =content_tag(:div, 'Include Dates:', :class => 'filter-element desc') + =select_tag("#{filter_label}_date", options_for_select(Search::DATE_OPTS), {:class => "easydropdown"}) =content_tag(:div, :class => 'filter-element wrapper') do -if :feed==filter_label - / @begin show filter =content_tag(:div, 'Show:', :class => 'filter-element desc') - =select_tag("#{filter_label}_show", options_for_select(Search::SHOW_OPTS), {:class => "easydropdown"}) - / @end show filter + =select_tag("#{filter_label}_show", options_for_select(Search::SHOW_OPTS), {:class => "easydropdown"}) -elsif :musician==filter_label - / @begin score filter =content_tag(:div, 'Latency:', :class => 'filter-element desc latency-or-distance') =content_tag(:div, :class => 'query-distance-params') do =select_tag("musician_query_score", options_for_select(Search::M_SCORE_OPTS, Search::M_SCORE_DEFAULT), {:class => 'easydropdown'}) @@ -53,26 +43,20 @@ #musician-search-city.filter-element.desc to %a#musician-change-filter-city{:href => "#"} - %span#musician-filter-city - / @end score filter + %span#musician-filter-city -elsif :jamtrack==filter_label - / @begin availability filter - =content_tag(:div, 'Availability:', :class => 'filter-element desc') - =select_tag("#{filter_label}_availability", options_for_select([['Any', '']].concat(JamRuby::JamTrack::SALES_REGION), 'United States'), {:class => "easydropdown"}) - / @end availability filter + / =content_tag(:div, 'Availability:', :class => 'filter-element desc') + =select_tag("#{filter_label}_availability", options_for_select([['Any Availability', '']].concat(JamRuby::JamTrack::SALES_REGION), 'United States'), {:class => "easydropdown"}) -else - / @begin distance filter =content_tag(:div, 'Within', :class => 'filter-element desc') =content_tag(:div, :class => 'query-distance-params') do -default_distance =:musician==filter_label ? Search::M_MILES_DEFAULT : Search::B_MILES_DEFAULT =select_tag("#{filter_label}_query_distance", options_for_select(Search::DISTANCE_OPTS, default_distance), {:class => 'easydropdown'}) =content_tag(:div, :class => 'filter-element desc') do - miles of #{content_tag(:span, current_user ? current_user.current_city(request.remote_ip) : '', :id => "#{filter_label}-filter-city")} - / @end distance filter + miles of #{content_tag(:span, current_user ? current_user.current_city(request.remote_ip) : '', :id => "#{filter_label}-filter-city")} -if :feed==filter_label .btn-refresh-holder %a.button-grey.btn-refresh-entries{:href => "/client#/feed"} REFRESH -elsif :musician==filter_label .btn-refresh-holder - %a.button-grey.btn-refresh-entries{:href => "/client#/musicians"} REFRESH -/ @end web_filter \ No newline at end of file + %a.button-grey.btn-refresh-entries{:href => "/client#/musicians"} REFRESH \ No newline at end of file diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb index d582595e4..2825ee519 100644 --- a/web/app/views/clients/index.html.erb +++ b/web/app/views/clients/index.html.erb @@ -38,8 +38,11 @@ <%= render "users/feed_music_session_ajax" %> <%= render "users/feed_recording_ajax" %> <%= render "jamtrack" %> +<%= render "jamtrack_landing" %> <%= render "shopping_cart" %> -<%= render "signin" %> +<%= render "checkout_signin" %> +<%= render "checkout_payment" %> +<%= render "checkout_order" %> <%= render "order" %> <%= render "feed" %> <%= render "bands" %> @@ -55,6 +58,7 @@ <%= render "account_profile_avatar" %> <%= render "account_audio_profile" %> <%= render "account_sessions" %> +<%= render "account_jamtracks" %> <%= render "account_session_detail" %> <%= render "account_session_properties" %> <%= render "inviteMusicians" %> @@ -112,20 +116,7 @@ JK.currentUserName = null; JK.currentUserMusician = null; JK.currentUserAdmin = false; - - // you need to be logged in to use this part of the interface. - // save original URL, and redirect to the home page - logger.debug("redirecting back to / because not logged in") - - var redirectPath= '?redirect-to=' + encodeURIComponent(JK.locationPath()); - if(gon.isNativeClient) { - window.location.href = '/signin' + redirectPath; - } - else { - window.location.href = '/' + redirectPath; - } - - <% end %> + <% end %> // Some things can't be initialized until we're connected. Put them here. @@ -174,6 +165,14 @@ var jamtrackAvailabilityDialog = new JK.JamtrackAvailabilityDialog(JK.app); jamtrackAvailabilityDialog.initialize(); + + var jamtrackLicenseDialog = new JK.JamtrackLicenseDialog(JK.app); + jamtrackLicenseDialog.initialize(); + + var jamtrackPaymentHistoryDialog = new JK.JamtrackPaymentHistoryDialog(JK.app); + jamtrackPaymentHistoryDialog.initialize(); + + var audioProfileInvalidDialog = new JK.AudioProfileInvalidDialog(JK.app); audioProfileInvalidDialog.initialize(); @@ -199,6 +198,9 @@ var accountSessionsScreen = new JK.AccountSessions(JK.app); accountSessionsScreen.initialize(); + var accountJamTracksScreen = new JK.AccountJamTracks(JK.app); + accountJamTracksScreen.initialize(); + var accountSessionDetailScreen = new JK.AccountSessionDetail(JK.app); accountSessionDetailScreen.initialize(JK.InvitationDialogInstance); @@ -261,14 +263,25 @@ var jamtrackScreen = new JK.JamTrackScreen(JK.app); jamtrackScreen.initialize(); + + + var jamtrackLanding = new JK.JamTrackLanding(JK.app); + jamtrackLanding.initialize(); + var shoppingCartScreen = new JK.ShoppingCartScreen(JK.app); shoppingCartScreen.initialize(); var checkoutSignInScreen = new JK.CheckoutSignInScreen(JK.app); checkoutSignInScreen.initialize(); - var OrderScreen = new JK.OrderScreen(JK.app); - OrderScreen.initialize(); + var checkoutPaymentScreen = new JK.CheckoutPaymentScreen(JK.app); + checkoutPaymentScreen.initialize(); + + var checkoutOrderScreen = new JK.CheckoutOrderScreen(JK.app); + checkoutOrderScreen.initialize(); + + // var OrderScreen = new JK.OrderScreen(JK.app); + // OrderScreen.initialize(); var findMusicianScreen = new JK.MusicianSearchFilter(); findMusicianScreen.init(JK.app); @@ -306,6 +319,8 @@ JK.ClientUpdateInstance.check() JK.app.initialRouting(); + + JK.hideCurtain(300); } @@ -321,10 +336,10 @@ JK.RecordingUtils.init(); - // Let's get things rolling... - if (JK.currentUserId) { + JK.app.initialize(); - JK.app.initialize(); + // Let's get things rolling... + if (JK.currentUserId) { JK.JamServer.registerMessageCallback(JK.MessageType.CLIENT_UPDATE, function(header, payload) { // do a client update early check upon initialization @@ -332,7 +347,7 @@ }); - JK.TickDuration('.feed-entry.music-session-history-entry .inprogress .tick-duration'); + JK.TickDuration('.feed-entry.music-session-history-entry .inprogress .tick-duration'); JK.JamServer.connect() // singleton here defined in JamServer.js .done(function() { @@ -345,6 +360,9 @@ // this ensures that there is always a CurrentSessionModel, even if it's for a non-active session JK.CurrentSessionModel = new JK.SessionModel(JK.app, JK.JamServer, window.jamClient, null); } + else { + _initAfterConnect(false); + } JK.bindHoverEvents(); }) diff --git a/web/app/views/dialogs/_dialogs.html.haml b/web/app/views/dialogs/_dialogs.html.haml index 7383a4400..990240721 100644 --- a/web/app/views/dialogs/_dialogs.html.haml +++ b/web/app/views/dialogs/_dialogs.html.haml @@ -25,6 +25,7 @@ = render 'dialogs/videoDialog' = render 'dialogs/friendSelectorDialog' = render 'dialogs/jamtrackAvailabilityDialog' += render 'dialogs/jamtrackLicenseDialog' = render 'dialogs/clientPreferencesDialog' = render 'dialogs/audioProfileInvalidDialog' = render 'dialogs/gettingStartedDialog' @@ -34,5 +35,7 @@ = render 'dialogs/adjustGearSpeedDialog' = render 'dialogs/openJamTrackDialog' = render 'dialogs/openBackingTrackDialog' += render 'dialogs/loginRequiredDialog' += render 'dialogs/jamtrackPaymentHistoryDialog' = render 'dialogs/genreSelectorDialog' -= render 'dialogs/recordingSelectorDialog' \ No newline at end of file += render 'dialogs/recordingSelectorDialog' diff --git a/web/app/views/dialogs/_jamtrackLicenseDialog.html.slim b/web/app/views/dialogs/_jamtrackLicenseDialog.html.slim new file mode 100644 index 000000000..a72c01c06 --- /dev/null +++ b/web/app/views/dialogs/_jamtrackLicenseDialog.html.slim @@ -0,0 +1,18 @@ +#jamtrack-license-dialog.dialog.dialog-overlay-sm layout='dialog' layout-id = 'jamtrack-license-dialog' + .content-head + h1 JamTrack License: + .dialog-inner + .content-body + .content-body-scroller + .paragraph + | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam dignissim ut nunc at hendrerit. Vestibulum semper risus a libero fermentum, molestie convallis risus faucibus. Ut molestie hendrerit orci, id laoreet turpis malesuada nec. Cras sem urna, commodo finibus sodales eu, scelerisque et ligula. Vivamus congue urna lobortis, volutpat ex non, facilisis ante. Maecenas laoreet lacus sit amet justo tempus sagittis. Proin eget libero est. Nullam vulputate finibus nibh nec malesuada. Proin at odio dui. Cras venenatis pharetra ipsum sit amet mollis. Vivamus enim lectus, venenatis sit amet velit at, condimentum euismod dolor. Sed ut tellus in lacus finibus maximus quis ac tortor. Nullam ac purus tincidunt, vestibulum magna vel, hendrerit nunc. Nam tincidunt velit ut est congue ultrices. Integer id magna vulputate, consequat ante et, gravida nibh. + .paragraph + | Etiam ac neque vel ex sagittis cursus ut a nulla. Praesent id pretium metus. Duis rhoncus egestas magna ut fringilla. Aenean et lobortis sem. Duis at turpis luctus, auctor lectus vitae, consectetur ante. Donec feugiat ullamcorper lacus eu ultricies. Sed vitae turpis arcu. Nam faucibus facilisis sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Quisque vel felis rutrum, elementum lacus euismod, ultricies leo. + .paragraph + | Etiam ac neque vel ex sagittis cursus ut a nulla. Praesent id pretium metus. Duis rhoncus egestas magna ut fringilla. Aenean et lobortis sem. Duis at turpis luctus, auctor lectus vitae, consectetur ante. Donec feugiat ullamcorper lacus eu ultricies. Sed vitae turpis arcu. Nam faucibus facilisis sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Quisque vel felis rutrum, elementum lacus euismod, ultricies leo. + .paragraph + | Etiam non nisi magna. Sed diam sem, vulputate sit amet odio quis, vulputate pharetra nunc. Morbi commodo lacus in leo semper semper. Sed nulla felis, consequat dignissim dictum eu, elementum eget massa. Nulla luctus condimentum magna. Vestibulum in interdum erat. In porttitor fermentum mi, ac tincidunt nisi interdum vulputate. + + .jamtrack_buttons + .right + a.button-grey class='btnCancel' layout-action='cancel' OK diff --git a/web/app/views/dialogs/_jamtrackPaymentHistoryDialog.html.slim b/web/app/views/dialogs/_jamtrackPaymentHistoryDialog.html.slim new file mode 100644 index 000000000..663d0f939 --- /dev/null +++ b/web/app/views/dialogs/_jamtrackPaymentHistoryDialog.html.slim @@ -0,0 +1,33 @@ +#jamtrack-payment-history-dialog.dialog.dialog-overlay-sm layout='dialog' layout-id = 'jamtrack-payment-history-dialog' + .content-head + h1 Payment History: + .dialog-inner + .content-body + .content-body-scroller + table.payment-table + thead + tr + th DATE + th AMOUNT + th STATUS + th PAYMENT_METHOD + th REFERENCE + tbody + tr: td colspan="5" Loading payment history... + + .jamtrack_buttons + .right + a.button-orange class='btnCancel' layout-action='cancel' OK + + script#template-payment-history-row type="text/template" + tr + td + | {{data.date}} + td + | ${{data.amount}} + td.capitalize + | {{data.status}} + td.capitalize + | {{data.payment_method}} + td + | {{data.reference}} diff --git a/web/app/views/dialogs/_loginRequiredDialog.html.slim b/web/app/views/dialogs/_loginRequiredDialog.html.slim new file mode 100644 index 000000000..4b979e68d --- /dev/null +++ b/web/app/views/dialogs/_loginRequiredDialog.html.slim @@ -0,0 +1,20 @@ +.dialog.dialog-overlay-sm layout='dialog' layout-id='login-required-dialog' id='login-required-dialog' + .content-head + = image_tag "content/icon_alert.png", {:width => 24, :height => 24, :class => 'content-icon' } + h1 Login Required + + .dialog-inner + p + a href="/signup" Sign Up + |  or  + a href="/signin" Sign In + |  to access most functionality on this page. + p + | However, you can browse for  + a class="go-to-jamtracks" href='/client#/jamtrack' JamTracks + |  without logging in. + br + .clearall + .buttons + .right + a.button-orange class='btnClose' layout-action='close' CLOSE \ No newline at end of file diff --git a/web/app/views/layouts/client.html.erb b/web/app/views/layouts/client.html.erb index 2c4238803..adee2a9d7 100644 --- a/web/app/views/layouts/client.html.erb +++ b/web/app/views/layouts/client.html.erb @@ -35,5 +35,7 @@ <%= yield %> <%= render "shared/ga" %> + <%= render "shared/recurly" %> + diff --git a/web/app/views/layouts/web.html.erb b/web/app/views/layouts/web.html.erb index c816bf518..f3cd75c75 100644 --- a/web/app/views/layouts/web.html.erb +++ b/web/app/views/layouts/web.html.erb @@ -37,10 +37,13 @@
    - <% unless @welcome_page %> - <%= render "users/user_dropdown" %> + <% if @no_user_dropdown %> <% else %> - <%= render "users/video_carousel" %> + <% unless @welcome_page %> + <%= render "users/user_dropdown" %> + <% else %> + <%= render "users/video_carousel" %> + <% end %> <% end %>
    @@ -50,8 +53,10 @@
    <% unless @welcome_page %> - <%= content_tag(:div, content_tag(:h1,'Play music together over the Internet as if in the same room'), :class => "landing-tag") %> - <%= content_tag(:div,'',:class => "clearall") %> +
    +

    Live music platform &
    social network for musicians

    +
    +
    <% end %>
    @@ -79,6 +84,7 @@ <%= render "clients/hoverRecording" %> <%= render "clients/help" %> <%= render "clients/listenBroadcast" %> + <%= render "clients/flash" %> <%= render 'dialogs/dialogs' %> diff --git a/web/app/views/shared/_recurly.html.slim b/web/app/views/shared/_recurly.html.slim new file mode 100644 index 000000000..d22bdabbd --- /dev/null +++ b/web/app/views/shared/_recurly.html.slim @@ -0,0 +1,4 @@ +script src="https://js.recurly.com/v3/recurly.js" + +javascript: + recurly.configure(gon.global.recurly_public_api_key) \ No newline at end of file diff --git a/web/app/views/users/_download_templates.html.erb b/web/app/views/users/_download_templates.html.erb deleted file mode 100644 index 6b8fad6e4..000000000 --- a/web/app/views/users/_download_templates.html.erb +++ /dev/null @@ -1,46 +0,0 @@ - - - - \ No newline at end of file diff --git a/web/app/views/users/_download_templates.html.slim b/web/app/views/users/_download_templates.html.slim new file mode 100644 index 000000000..21d886117 --- /dev/null +++ b/web/app/views/users/_download_templates.html.slim @@ -0,0 +1,46 @@ + +script type="text/template" id="client-download-blurb-contents" + .downloads + + a href="{{data.uri}}" class="current-os-download" data-platform="{{data.platform}}" + + .downloads-container + + h5 SYSTEM REQUIREMENTS: + | {% if(data.platform == "Win32") { %} + ul.windows-requirements + li Windows 7 or 8, 64-bit (32-bit not supported) + li Dual core processor or higher + li 75MB hard disk space for app + li External audio interface recommended (but you can start with built-in mic and & headphone jack) + li Ethernet port for real-time online sessions (WiFi not recommended) + li Broadband Internet service with 1Mbps uplink bandwidth for real-time online sessions + | {% } else if(data.platform == "MacOSX") { %} + ul.mac-requirements + li Mac OS X 10.7 or higher, 64-bit + li Dual-core processor or higher + li 75MB hard disk space for app + li External audio interface recommended (but you can start with built-in mic and & headphone jack) + li Ethernet port for real-time online sessions (WiFi not recommended) + li Broadband Internet service with 1Mbps uplink bandwidth for real-time online sessions + | {% } else { %} + ul.linux-requirements + li Linux is not yet supported + | {% } %} + + + + .hidden.hidden-images + = image_tag("content/button_download_mac.png", :alt => "download mac", :size => "348x92", "data-purpose" => "mac") + = image_tag("content/button_download_windows.png", :alt => "download windows", :size => "348x92", "data-purpose" => "windows") + = image_tag("content/button_download_linux.png", :alt => "download linux", :size => "348x92", "data-purpose" => "linux") + +script type="text/template" id="client-download-select-others" + .download-box + .download-others + a.choose-other-platform href="#" data-order="1" data-platform="{{data.platform1}}" + | Need a different version? + br + | Click here for to get JamKazam + br + | for {{data.platformDisplay1}} \ No newline at end of file diff --git a/web/app/views/users/_downloads.html.erb b/web/app/views/users/_downloads.html.erb deleted file mode 100644 index 0fe47521a..000000000 --- a/web/app/views/users/_downloads.html.erb +++ /dev/null @@ -1,43 +0,0 @@ - - -
    -
    -
    - -
    -
    - -
    - -
    - -
    -<% content_for :after_black_bar do %> -
    -

    SYSTEM REQUIREMENTS:


    -

    A short summary of requirements follows. For a more detailed explanation of system requirements, please review our Minimum System Requirements knowledgebase article.

    -
      -
    • Windows 64-bit operating system (Win 7 & 8 tested, Win XP and Vista like to work but not officially supported
    • -
    • Dual-core processor or higher
    • -
    • Ethernet port for Internet (we strongly advise that you not use Wi-Fi)
    • -
    • 74MB hard disk space for app, plus any space needed for recordings
    • -
    • Audio interface (best to use an audio interface device that gets your music into your computer, else can use built-in mic & headphones on your computer to get started)
    • -
    • Broadband Internet service with 1Mbps uplink bandwidth
    • -
    -
      -
    • Mac OS X 64-bit operating system 10.7 or higher
    • -
    • Dual-core processor or higher
    • -
    • Ethernet port for Internet (we strongly advise that you not use Wi-Fi)
    • -
    • 74MB hard disk space for app, plus any space needed for recordings
    • -
    • Audio interface (best to use an audio interface device that gets your music into your computer, else can use built-in mic & headphones on your computer to get started)
    • -
    • Broadband Internet service with 1Mbps uplink bandwidth
    • -
    -
      -
    • Linux is not yet supported
    • -
    -
    -<%end%> - -<%= render "users/download_templates" %> - - diff --git a/web/app/views/users/_downloads.html.slim b/web/app/views/users/_downloads.html.slim new file mode 100644 index 000000000..9323df562 --- /dev/null +++ b/web/app/views/users/_downloads.html.slim @@ -0,0 +1,64 @@ +// used by congrats_musician, and downloads +- provide(:page_name, 'downloads') +- provide(:title, 'Download') + +.w100 + .download-app + .spinner-large + + h2.create-account-header + .badge-number 2 + | Download the free JamKazam app + + .download-content + .download-entreaty + + p You need the JamKazam application to: + ul + li Play music with others in real time on the JamKazam platform + li Make audio recordings and share them via Facebook or URL + li Make video recordings and share them via YouTube or URL + li Live broadcast your sessions to family, friends, and fans + li Have full control over your JamTracks multi-track recordings + + p.click-to-download Click the button below to download the JamKazam application installer. + .downloads-blurb + + .jamtracks + + h2.shop-jamtracks + .badge-number 3 + | Get your free JamTrack + span.special-value + |   ($1.99 value) + + + .jamtrack-content + .jamtrack-entreaty + + p JamTracks are multi-track pro recordings you can use to: + ul + li Solo any part to hear and learn it + li Mute the part you want to play, and play along with the rest + li Make audio recordings and share them via Facebook or URL + li Make video recordings and share them via YouTube or URL + li Go online to play real time sessions, with others playing parts + p + | Watch the video below to learn more. Then click the button to shop + |  for your first JamTrack - free! Add it to your shopping cart, and we'll + |  make it free during the checkout process. Free offer good for 1 week only! + .video-container + iframe src="//www.youtube.com/embed/gAJAIHMyois" frameborder="0" allowfullscreen + + a.go-jamtrack-shopping href="/client#/jamtrack" rel="external" + | Shop for free + br + | JamTrack now! + + br clear="all" + + + + = render "users/download_templates" + + diff --git a/web/app/views/users/congratulations_fan.html.erb b/web/app/views/users/congratulations_fan.html.erb index 7c0735c9d..9fb302af9 100644 --- a/web/app/views/users/congratulations_fan.html.erb +++ b/web/app/views/users/congratulations_fan.html.erb @@ -1,13 +1,7 @@ <% provide(:title, 'Congratulations') %> -
    -
    Congratulations!
    +<%= render "users/downloads" %> -

    You have successfully registered as a JamKazam fan.

    - -
    <%= link_to "PROCEED TO JAMKAZAM SITE", client_path, :class =>"button-orange m0" %>
    - -
    \ No newline at end of file + $(function() { window.congratulations.initialize(true, jQuery.QueryString["type"]) }) + diff --git a/web/app/views/users/congratulations_musician.html.erb b/web/app/views/users/congratulations_musician.html.erb index f2684fff2..9fb302af9 100644 --- a/web/app/views/users/congratulations_musician.html.erb +++ b/web/app/views/users/congratulations_musician.html.erb @@ -1,16 +1,7 @@ <% provide(:title, 'Congratulations') %> -<% if @nativeClient %> -
    -
    Congratulations!
    -

    You have successfully registered as a musician.

    -
    <%= link_to "PROCEED TO JAMKAZAM SITE", client_path, :class =>"button-orange m0" %>
    -
    -<% else %> - <%= render "users/downloads" %> -<% end %> - +<%= render "users/downloads" %> diff --git a/web/app/views/users/downloads.html.erb b/web/app/views/users/downloads.html.erb index ddd7d5f7c..87a3f7d40 100644 --- a/web/app/views/users/downloads.html.erb +++ b/web/app/views/users/downloads.html.erb @@ -3,5 +3,5 @@ <%= render "users/downloads" %> \ No newline at end of file diff --git a/web/app/views/users/new.html.erb b/web/app/views/users/new.html.erb index 09a4e9f08..357155e7b 100644 --- a/web/app/views/users/new.html.erb +++ b/web/app/views/users/new.html.erb @@ -2,7 +2,7 @@ <% provide(:title, 'Register') %>
    -

    Create a JamKazam account

    + <%= form_for(@user, :url => @signup_postback, :method => :post) do |f| %> @@ -137,8 +137,8 @@ -->
    - <%= f.submit "CREATE ACCOUNT", class: "right button-orange" %> - <%= link_to "CANCEL", root_path, :class=>'button-grey right' %> + <%= f.submit "CREATE ACCOUNT", class: "button-orange" %> + <%= link_to "CANCEL", root_path, :class=>'button-grey' %>


    diff --git a/web/config/application.rb b/web/config/application.rb index e0bf20211..aac520b7d 100644 --- a/web/config/application.rb +++ b/web/config/application.rb @@ -317,5 +317,6 @@ if defined?(Bundler) config.show_jamblaster_kickstarter_link = true config.metronome_available = true config.backing_tracks_available = true + config.one_free_jamtrack_per_user = true end end diff --git a/web/config/initializers/gon.rb b/web/config/initializers/gon.rb index 228bff6b1..ed4f92a4f 100644 --- a/web/config/initializers/gon.rb +++ b/web/config/initializers/gon.rb @@ -10,4 +10,6 @@ Gon.global.influxdb_port = Rails.application.config.influxdb_port Gon.global.influxdb_database = Rails.application.config.influxdb_database Gon.global.influxdb_username = Rails.application.config.influxdb_unsafe_username Gon.global.influxdb_password = Rails.application.config.influxdb_unsafe_password +Gon.global.recurly_public_api_key = Rails.application.config.recurly_public_api_key +Gon.global.one_free_jamtrack_per_user = Rails.application.config.one_free_jamtrack_per_user Gon.global.env = Rails.env diff --git a/web/config/routes.rb b/web/config/routes.rb index be93d6b68..b10262fc3 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -208,6 +208,7 @@ SampleApp::Application.routes.draw do match '/jamtracks' => 'api_jam_tracks#index', :via => :get, :as => 'api_jam_tracks_list' match '/jamtracks/purchased' => 'api_jam_tracks#purchased', :via => :get, :as => 'api_jam_tracks_purchased' match '/jamtracks/download/:id' => 'api_jam_tracks#download', :via => :get, :as => 'api_jam_tracks_download' + match '/jamtracks/played/:id' => 'api_jam_tracks#played', :via => :post, :as => 'api_jam_tracks_played' match '/jamtracks/enqueue/:id' => 'api_jam_tracks#enqueue', :via => :post, :as => 'api_jam_tracks_enqueue' match '/jamtracks/rights/:id' => 'api_jam_tracks#show_jam_track_right', :via => :get, :as => 'api_jam_tracks_show_right' match '/jamtracks/keys' => 'api_jam_tracks#keys', :via => :post, :as => 'api_jam_tracks_keys' @@ -259,6 +260,7 @@ SampleApp::Application.routes.draw do match '/recurly/create_account' => 'api_recurly#create_account', :via => :post match '/recurly/delete_account' => 'api_recurly#delete_account', :via => :delete match '/recurly/get_account' => 'api_recurly#get_account', :via => :get + match '/recurly/payment_history' => 'api_recurly#payment_history', :via => :get #match '/recurly/get_subscription' => 'api_recurly#get_subscription', :via => :get match '/recurly/update_account' => 'api_recurly#update_account', :via => :put match '/recurly/billing_info' => 'api_recurly#billing_info', :via => :get diff --git a/web/lib/user_manager.rb b/web/lib/user_manager.rb index 5c36c6974..75ce77b07 100644 --- a/web/lib/user_manager.rb +++ b/web/lib/user_manager.rb @@ -26,8 +26,12 @@ class UserManager < BaseManager fb_signup = options[:fb_signup] signup_confirm_url = options[:signup_confirm_url] affiliate_referral_id = options[:affiliate_referral_id] - recaptcha_failed=(fb_signup) ? false : !@google_client.verify_recaptcha(options[:recaptcha_response]) - + any_user = options[:any_user] + recaptcha_failed = false + unless options[:skip_recaptcha] # allow callers to opt-of recaptcha + recaptcha_failed = fb_signup ? false : !@google_client.verify_recaptcha(options[:recaptcha_response]) + end + user = User.new # check if we have disabled open signup for this site. open == invited users can still get in @@ -62,11 +66,9 @@ class UserManager < BaseManager invited_user: invited_user, fb_signup: fb_signup, signup_confirm_url: signup_confirm_url, - affiliate_referral_id: affiliate_referral_id) - - - return user - #end + affiliate_referral_id: affiliate_referral_id, + any_user: any_user) + user end def signup_confirm(signup_token, remote_ip=nil) diff --git a/web/spec/controllers/api_jam_tracks_controller_spec.rb b/web/spec/controllers/api_jam_tracks_controller_spec.rb index 357cb8737..e70578564 100644 --- a/web/spec/controllers/api_jam_tracks_controller_spec.rb +++ b/web/spec/controllers/api_jam_tracks_controller_spec.rb @@ -15,6 +15,7 @@ describe ApiJamTracksController do end before(:each) do + JamTrackRight.destroy_all JamTrack.destroy_all @user = FactoryGirl.create(:user) @jam_track = FactoryGirl.create(:jam_track) @@ -60,19 +61,6 @@ describe ApiJamTracksController do json["next"].should be_nil json["jamtracks"].length.should == 2 end - - it "lists owned tracks" do - get :downloads - response.should be_success - json = JSON.parse(response.body) - json['downloads'].should have(0).items - - right = JamTrackRight.create(:user=>@user, :jam_track=>@jam_track) - get :downloads - response.should be_success - json = JSON.parse(response.body) - json['downloads'].should have(1).items - end it "finds a download" do #get "/download/#{right.id}/" @@ -107,9 +95,32 @@ describe ApiJamTracksController do end end + describe "jamtrack plays" do + it "handle api call success" do + post :played, { id: @jam_track.id, user: @user } + expect(response.status).to eq(201) + json = JSON.parse(response.body) + expect(json.length).to eq(0) + end + + it "handle api call 400" do + post :played, { user: @user } + expect(response.status).to eq(400) + json = JSON.parse(response.body) + expect(/JamTrack ID required/).to match(json['message']) + end + + it "handle api call 500" do + post :played, { id: 999, user: @user } + expect(response.status).to eq(500) + json = JSON.parse(response.body) + expect(/Unexpected error occurred/).to match(json['message']) + end + + end + describe "with a JamTrack" do before(:each) do - JamTrackRight.destroy_all # Create a working JamTrack for these tests. The integrity # of this process is checked in other tests: @ogg_path = File.join('spec', 'files', 'on.ogg') @@ -117,15 +128,16 @@ describe ApiJamTracksController do jam_track_track = @jam_track.jam_track_tracks.first # 48 kHz: - uploader = JamTrackTrackUploader.new(jam_track_track, :url_48) - uploader.store!(File.open(@ogg_path, 'rb')) - + + @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) + # 44 kHz: - uploader = JamTrackTrackUploader.new(jam_track_track, :url_44) - uploader.store!(File.open(File.join('spec', 'files', 'off.ogg'), 'rb')) + @s3.upload(jam_track_track.manually_uploaded_filename(:url_44), File.join('spec', 'files', 'off.ogg')) + jam_track_track[:url_44] = jam_track_track.manually_uploaded_filename(:url_44) #jam_track_track.url.store!(File.open(ogg_path, "rb")) - jam_track_track.save! + jam_track_track.save! jam_track_track.reload ResqueSpec.reset! end diff --git a/web/spec/controllers/api_recurly_spec.rb b/web/spec/controllers/api_recurly_spec.rb index fb0eeabfd..bc423f899 100644 --- a/web/spec/controllers/api_recurly_spec.rb +++ b/web/spec/controllers/api_recurly_spec.rb @@ -35,7 +35,7 @@ describe ApiRecurlyController, :type=>:controller do it "should send correct error" do @billing_info[:number]='121' - post :create_account, {:format => 'json', :billing_info=>@billing_info} + post :create_account, {:format => 'json', :billing_info=>@billing_info, reuse_card_this_time: false, reuse_card_next_time: false} response.status.should == 404 body = JSON.parse(response.body) body['errors'].should have(1).items @@ -43,36 +43,39 @@ describe ApiRecurlyController, :type=>:controller do end it "should create account" do - post :create_account, {:format => 'json'} - response.should be_success + post :create_account, {:format => 'json',billing_info: @billing_info, reuse_card_this_time: false, reuse_card_next_time: false} + response.should be_success + body = JSON.parse(response.body) + response.should be_success + body['billing_info']['first_name'].should eq(@user.first_name) end - it "should retrieve account" do - post :create_account, {:format => 'json'} + it "should retrieve account with no billing info" do + post :create_account, {:format => 'json', reuse_card_this_time: false, reuse_card_next_time: false} response.should be_success get :get_account body = JSON.parse(response.body) response.should be_success - body['email'].should eq(@user.email) + body['billing_info'].should be_nil end it "should update account" do - post :create_account + post :create_account, {:format => 'json', billing_info: @billing_info, reuse_card_this_time: false, reuse_card_next_time: false} response.should be_success body = JSON.parse(response.body) - body['first_name'].should eq("Person") + body['billing_info']['first_name'].should eq("Person") - @user.update_attribute(:first_name, "Thing") controller.current_user = @user - put :update_account + @billing_info[:first_name] = "Thing" + put :update_account, {:format => 'json', billing_info: @billing_info} body = JSON.parse(response.body) - body['first_name'].should eq("Thing") + body['billing_info']['first_name'].should eq("Thing") get :get_account, { :format => 'json'} response.should be_success body = JSON.parse(response.body) - body['first_name'].should eq("Thing") + body['billing_info']['first_name'].should eq("Thing") end # Note: We don't have any subscriptions yet: @@ -95,13 +98,13 @@ describe ApiRecurlyController, :type=>:controller do # $enable_tracing = true - post :create_account + post :create_account, {:format => 'json', billing_info: @billing_info, reuse_card_this_time: false, reuse_card_next_time: false} response.should be_success body = JSON.parse(response.body) - body['first_name'].should eq("Person") + body['billing_info']['first_name'].should eq("Person") @billing_info[:state] = "NE" - put :update_billing_info, {:format => 'json', :billing_info=>@billing_info} + put :update_billing_info, {format: 'json', billing_info: @billing_info} response.should be_success body = JSON.parse(response.body) diff --git a/web/spec/controllers/api_shopping_carts_controller_spec.rb b/web/spec/controllers/api_shopping_carts_controller_spec.rb new file mode 100644 index 000000000..4c9f69079 --- /dev/null +++ b/web/spec/controllers/api_shopping_carts_controller_spec.rb @@ -0,0 +1,99 @@ +require 'spec_helper' + + + +describe ApiShoppingCartsController do + + render_views + + let(:jam_track) { FactoryGirl.create(:jam_track) } + + before(:each) do + ShoppingCart.delete_all + controller.anonymous_user.should be_nil + controller.current_user.should be_nil + end + + describe "logged in" do + + let(:user) { FactoryGirl.create(:user) } + + before(:each) do + controller.current_user = user + controller.anonymous_user = nil + end + + it "add_jamtrack" do + post :add_jamtrack, {:format => 'json', id: jam_track.id} + response.status.should == 201 + end + + it "index" do + cart = ShoppingCart.create(user, jam_track) + + get :index, {:format => 'json'} + response.status.should == 200 + response_body = JSON.parse(response.body) + response_body.length.should eq(1) + response_body[0]["id"].should eq(cart.id) + end + + it "remove_cart" do + cart = ShoppingCart.create(user, jam_track) + delete :remove_cart, {:format => 'json', id: cart.id} + response.status.should == 204 + ShoppingCart.find_by_id(cart.id).should be_nil + end + + it "clears cart" do + cart = ShoppingCart.create(user, jam_track) + delete :clear_all, {:format => 'json'} + response.status.should == 200 + ShoppingCart.find_by_id(cart.id).should be_nil + end + end + + describe "anonymous" do + + let(:user) { AnonymousUser.new('abc') } + + before(:each) do + controller.current_user = nil + controller.anonymous_user = user + end + + it "add_jamtrack" do + post :add_jamtrack, {:format => 'json', id: jam_track.id} + response.status.should == 201 + end + + it "index" do + cart = ShoppingCart.create(user, jam_track) + + get :index, {:format => 'json'} + response.status.should == 200 + response_body = JSON.parse(response.body) + response_body.length.should eq(1) + response_body[0]["id"].should eq(cart.id) + end + + it "remove_cart" do + cart = ShoppingCart.create(user, jam_track) + delete :remove_cart, {:format => 'json', id: cart.id} + response.status.should == 204 + ShoppingCart.find_by_id(cart.id).should be_nil + end + + it "clears cart" do + cart = ShoppingCart.create(user, jam_track) + delete :clear_all, {:format => 'json'} + response.status.should == 200 + ShoppingCart.find_by_id(cart.id).should be_nil + end + end + + describe "cookieless" do + + end + +end diff --git a/web/spec/factories.rb b/web/spec/factories.rb index 59a8089fa..d625ec53c 100644 --- a/web/spec/factories.rb +++ b/web/spec/factories.rb @@ -20,6 +20,7 @@ FactoryGirl.define do terms_of_service true subscribe_email true last_jam_audio_latency 5 + reuse_card true factory :fan do musician false diff --git a/web/spec/features/authentication_pages_spec.rb b/web/spec/features/authentication_pages_spec.rb index 7aea9cb9d..70aae8a2b 100644 --- a/web/spec/features/authentication_pages_spec.rb +++ b/web/spec/features/authentication_pages_spec.rb @@ -62,7 +62,8 @@ describe "Authentication", :js => true, :type => :feature, :capybara_feature => find('.userinfo .sign-out a').trigger(:click) end - it { find('h1', text: 'Play music together over the Internet as if in the same room') } + # after logging out, we keep you at /client + it { find('#profile a.signin', text: 'Sign Up') } end end end diff --git a/web/spec/features/checkout_spec.rb b/web/spec/features/checkout_spec.rb new file mode 100644 index 000000000..8e72631fd --- /dev/null +++ b/web/spec/features/checkout_spec.rb @@ -0,0 +1,726 @@ +require 'spec_helper' + +describe "Checkout", :js => true, :type => :feature, :capybara_feature => true do + + let(:user) { FactoryGirl.create(:user) } + let(:jamtrack_acdc_backinblack) { @jamtrack_acdc_backinblack } + let(:jamtrack_pearljam_evenflow) { @jamtrack_pearljam_evenflow } + + let(:billing_info) { + { + first_name: 'Seth', + last_name: 'Call', + address1: '10704 Buckthorn Drive', + city: 'Austin', + state: 'Texas', + country: 'US', + zip: '78759', + number: '4111111111111111', + month: '08', + year: '2017', + verification_value: '012' + } + } + + def create_account(user, billing_info) + @recurlyClient.create_account(user, billing_info) + @created_accounts << user + end + + before(:all) do + Capybara.javascript_driver = :poltergeist + Capybara.current_driver = Capybara.javascript_driver + Capybara.default_wait_time = 20 # these tests are SLOOOOOW + + @recurlyClient = RecurlyClient.new + @created_accounts = [] + + @jamtrack_acdc_backinblack = FactoryGirl.create(:jam_track, name: 'Back in Black', original_artist: 'AC/DC', sales_region: 'United States', make_track: true, plan_code: 'jamtrack-acdc-backinblack') + @jamtrack_pearljam_evenflow = FactoryGirl.create(:jam_track, name: 'Even Flow', original_artist: 'Pearl Jam', sales_region: 'United States', make_track: true, plan_code: 'jamtrack-pearljam-evenflow') + + # make sure plans are there + @recurlyClient.create_jam_track_plan(@jamtrack_acdc_backinblack) unless @recurlyClient.find_jam_track_plan(@jamtrack_acdc_backinblack) + @recurlyClient.create_jam_track_plan(@jamtrack_pearljam_evenflow) unless @recurlyClient.find_jam_track_plan(@jamtrack_pearljam_evenflow) + + end + + + before(:each) do + ShoppingCart.delete_all + User.delete_all + + stub_const("APP_CONFIG", web_config) + end + + after(:each) do + @created_accounts.each do |user| + if user.recurly_code + begin + @account = Recurly::Account.find(user.recurly_code) + if @account.present? + @account.destroy + end + rescue + end + end + end + + end + + + def verify_nav(selected) + + 3.times do |i| + badge = i + 1 + + if badge == selected + find('.badge-number', text:badge) + else + find('.badge-number.disabled', text:badge) + end + end + end + + describe "Checkout Signin" do + + it "allows user to log in on the signin page" do + + visit '/client#/checkoutSignin' + + find('h3', text: 'ALREADY A MEMBER OF THE JAMKAZAM COMMUNITY?') + verify_nav(1) + + + # try a bogus user/pass first + fill_in "email", with: user.email + fill_in "password", with: 'wrong' + find('.signin-submit').trigger(:click) + + find('.login-error-msg', text: 'Invalid login') + + # try successfully + fill_in "email", with: user.email + fill_in "password", with: user.password + find('.signin-submit').trigger(:click) + + # this should take us to the payment screen + find('p.payment-prompt') + end + + it "allows user to skip login and go to payment screen" do + + visit '/client#/checkoutSignin' + + find('h3', text: 'ALREADY A MEMBER OF THE JAMKAZAM COMMUNITY?') + verify_nav(1) + + # skip to payment without signing in + find('a.btnNext').trigger(:click) + + # this should take us to the payment screen + find('p.payment-prompt') + end + + it "indicates already logged in" do + fast_signin(user, '/client#/checkoutSignin') + + # verify that the signin page shows, but indicates to the user that they are already signed in + find('h3', text: 'YOU ARE ALREADY LOGGED IN') + + verify_nav(1) + + find('p.carry-on-prompt', text: 'You can move on to the next step of checkout.') + + # let them move on to the next step + find('a.btnNext').trigger(:click) + find('p.payment-prompt') + end + end + + describe "Checkout Payment" do + it "allows anonymous to visit" do + + visit '/client#/checkoutPayment' + + find('p.payment-prompt.free-jamtrack') + + find('.jamkazam-account-signup') + + # try to submit, and see slew of errors + find('#payment-info-next').trigger(:click) + + find('#divBillingFirstName.error .error-text', text: 'First Name is required') + find('#divBillingLastName.error .error-text', text: 'Last Name is required') + find('#divBillingAddress1.error .error-text', text: 'Address is required') + find('#divBillingCity.error .error-text', text: 'City is required') + find('#divBillingState.error .error-text', text: 'State is required') + find('#divBillingZip.error .error-text', text: 'Zip Code is required') + find('#divCardNumber.error .error-text', text: 'Card Number is required') + find('#divCardVerify.error .error-text', text: 'Card Verification Value is required') + + # fill out all billing info, but not account info + fill_in 'billing-first-name', with: 'Seth' + fill_in 'billing-last-name', with: 'Call' + fill_in 'billing-address1', with: '10704 Buckthorn Drive' + fill_in 'billing-city', with: 'Austin' + fill_in 'billing-state', with: 'Texas' + fill_in 'billing-zip', with: '78759' + fill_in 'card-number', with: '4111111111111111' + fill_in 'card-verify', with: '012' + + # try to submit, and see new account errors + find('#payment-info-next').trigger(:click) + + find('#divJamKazamEmail.error .error-text', text: "can't be blank,is invalid") + find('#divJamKazamPassword.error .error-text', text: "is too short (minimum is 6 characters)") + find('#divJamKazamTos.error .error-text', text: "must be accepted") + + # verify that all filled out fields have had errors removed + find('#divBillingFirstName').has_no_css?('.error') + find('#divBillingLastName').has_no_css?('.error') + find('#divBillingAddress1').has_no_css?('.error') + find('#divBillingCity').has_no_css?('.error') + find('#divBillingState').has_no_css?('.error') + find('#divBillingZip').has_no_css?('.error') + find('#divCardNumber').has_no_css?('.error') + find('#divCardVerify').has_no_css?('.error') + + # fill in user/email/tos + fill_in 'email', with: 'seth@jamkazam.com' + fill_in 'password', with: 'jam123' + find('#divJamKazamTos ins.iCheck-helper').trigger(:click) # accept TOS + + # try to submit, and see order page + find('#payment-info-next').trigger(:click) + + # find empty shopping cart prompt notice + find('p.empty-cart-prompt') + + user.reload + user.reuse_card.should be_true + end + + it "shows card error correctly" do + fast_signin(user, '/client#/checkoutPayment') + + find('p.payment-prompt.free-jamtrack') + + expect(page).to_not have_selector('.jamkazam-account-signup') + + # fill out all billing info, but not account info + fill_in 'billing-first-name', with: 'Seth' + fill_in 'billing-last-name', with: 'Call' + fill_in 'billing-address1', with: '10704 Buckthorn Drive' + fill_in 'billing-city', with: 'Austin' + fill_in 'billing-state', with: 'Texas' + fill_in 'billing-zip', with: '78759' + fill_in 'card-number', with: '4000-0000-0000-0002' + fill_in 'card-verify', with: '012' + + # try to submit, and see slew of errors + find('#payment-info-next').trigger(:click) + + find('#payment_error', text:'Your transaction was declined. Please use a different card or contact your bank.') + end + + it "allows billing info submit for existing user" do + + fast_signin(user, '/client#/checkoutPayment') + + find('p.payment-prompt.free-jamtrack') + + expect(page).to_not have_selector('.jamkazam-account-signup') + + # try to submit, and see slew of errors + find('#payment-info-next').trigger(:click) + + find('#divBillingAddress1.error .error-text', text: 'Address is required') + find('#divBillingZip.error .error-text', text: 'Zip Code is required') + find('#divCardNumber.error .error-text', text: 'Card Number is required') + find('#divCardVerify.error .error-text', text: 'Card Verification Value is required') + + # fill out all billing info, but not account info + fill_in 'billing-first-name', with: 'Seth' + fill_in 'billing-last-name', with: 'Call' + fill_in 'billing-address1', with: '10704 Buckthorn Drive' + fill_in 'billing-city', with: 'Austin' + fill_in 'billing-state', with: 'Texas' + fill_in 'billing-zip', with: '78759' + fill_in 'card-number', with: '4111111111111111' + fill_in 'card-verify', with: '012' + + # try to submit, and see order page + find('#payment-info-next').trigger(:click) + + # find empty shopping cart prompt notice + find('p.empty-cart-prompt') + + user.reload + user.reuse_card.should be_true + end + + it "allows user to specify don't save card" do + + fast_signin(user, '/client#/checkoutPayment') + + find('p.payment-prompt.free-jamtrack') + + expect(page).to_not have_selector('.jamkazam-account-signup') + + # fill out all billing info, but not account info + fill_in 'billing-first-name', with: 'Seth' + fill_in 'billing-last-name', with: 'Call' + fill_in 'billing-address1', with: '10704 Buckthorn Drive' + fill_in 'billing-city', with: 'Austin' + fill_in 'billing-state', with: 'Texas' + fill_in 'billing-zip', with: '78759' + fill_in 'card-number', with: '4111111111111111' + fill_in 'card-verify', with: '012' + find('.save-card-checkbox ins.iCheck-helper').trigger(:click) # don't accept re-use card default + + # try to submit, and see order page + find('#payment-info-next').trigger(:click) + + # find empty shopping cart prompt notice + find('p.empty-cart-prompt') + + user.reload + user.reuse_card.should be_false + end + + it "payment shows saved card info correctly if user has billing info and reuse_card set to true" do + + ShoppingCart.add_jam_track_to_cart(user, jamtrack_acdc_backinblack) + + user.reuse_card = true + @recurlyClient.create_account(user, billing_info) + user.has_redeemable_jamtrack = true + user.save! + + fast_signin(user, '/client#/checkoutPayment') + + # ok, the user has a free jamtrack... verify that display confirms this + find('p.payment-prompt.free-jamtrack') + + # verify that all billing info looks disabled + have_field('billing-first-name', disabled: true) + have_field('billing-last-name', disabled: true) + have_field('billing-address1', disabled: true) + have_field('billing-address2', disabled: true) + have_field('billing-city', disabled: true) + have_field('billing-state', disabled: true) + have_field('billing-zip', disabled: true) + have_field('billing-country', disabled: true) + have_field('card-number', disabled: true) + have_field('card_expire-date_2i', disabled: true) + have_field('card_expire-date_1i', disabled: true) + have_field('card-number', disabled: true) + have_field('card-verify', disabled: true) + + # verify that the use current card checkbox is checked, and that the 'save card' checkbox is checking + find('#reuse-existing-card').checked?.should be_true + find('#save-card:checked').checked?.should be_true + + # then uncheck 'reuse-existing-card', which should re-enable all the fields that were just disabled + find('.reuse-existing-card-checkbox ins.iCheck-helper').trigger(:click) + + # verify that all billing info looks enabled now, since 'reuse-existing-card' was unchecked + have_field('billing-first-name', disabled: false) + have_field('billing-last-name', disabled: false) + have_field('billing-address1', disabled: false) + have_field('billing-address2', disabled: false) + have_field('billing-city', disabled: false) + have_field('billing-state', disabled: false) + have_field('billing-zip', disabled: false) + have_field('billing-country', disabled: false) + have_field('card-number', disabled: false) + have_field('card_expire-date_2i', disabled: false) + have_field('card_expire-date_1i', disabled: false) + have_field('card-number', disabled: false) + have_field('card-verify', disabled: false) + + # ok, we want to fiddle some values, and later prove that they will be ignored once we set reuse-existing-card back to checked + fill_in 'billing-first-name', with: 'Bobby' + fill_in 'billing-last-name', with: 'Junk' + fill_in 'billing-address1', with: '10702 Buckthorn Drive' + + # flip it back to reuse existing + find('.reuse-existing-card-checkbox ins.iCheck-helper').trigger(:click) + + # hit next... we should move on to the payment screen + + # try to submit, and see order page + find('#payment-info-next').trigger(:click) + + # find empty shopping cart prompt notice + find('p.order-prompt') + + account = @recurlyClient.get_account(user) + account.billing_info.address1.should eq(billing_info[:address1]) + account.billing_info.first_name.should eq(billing_info[:first_name]) + account.billing_info.last_name.should eq(billing_info[:last_name]) + + find('.order-items-value.sub-total', text:'0.00') + find('.order-items-value.taxes', text:'0.00') + find('.order-items-value.order-total', text:'0.00') + end + + it "payment allows user to enter new billing info" do + + ShoppingCart.add_jam_track_to_cart(user, jamtrack_acdc_backinblack) + + user.reuse_card = true + @recurlyClient.create_account(user, billing_info) + user.has_redeemable_jamtrack = true + user.save! + + fast_signin(user, '/client#/checkoutPayment') + + # ok, the user has a free jamtrack... verify that display confirms this + find('p.payment-prompt.free-jamtrack') + + # verify that the use current card checkbox is checked, and that the 'save card' checkbox is checking + find('#reuse-existing-card').checked?.should be_true + find('#save-card:checked').checked?.should be_true + + # then uncheck 'reuse-existing-card', which should re-enable all the fields that were just disabled + find('.reuse-existing-card-checkbox ins.iCheck-helper').trigger(:click) + + # ok, we want to fiddle some values, and later prove that they will be ignored once we set reuse-existing-card back to checked + fill_in 'billing-first-name', with: 'Bobby' + fill_in 'billing-last-name', with: 'Junk' + fill_in 'billing-address1', with: '10702 Buckthorn Drive' + fill_in 'card-number', with: '4111111111111111' + fill_in 'card-verify', with: '013' + + # hit next... we should move on to the payment screen + + # try to submit, and see order page + find('#payment-info-next').trigger(:click) + + # find empty shopping cart prompt notice + find('p.order-prompt') + + account = @recurlyClient.get_account(user) + account.billing_info.first_name.should eq('Bobby') + account.billing_info.last_name.should eq('Junk') + account.billing_info.address1.should eq('10702 Buckthorn Drive') + + find('.order-items-value.sub-total', text:'0.00') + find('.order-items-value.taxes', text:'0.00') + find('.order-items-value.order-total', text:'0.00') + end + + + it "user with no redeemable jamtrack is not show the free-jamtrack prompt" do + user.has_redeemable_jamtrack = false + user.save! + + fast_signin(user, '/client#/checkoutPayment') + + # ok, the user has a free jamtrack... verify that display confirms this + find('p.payment-prompt.no-free-jamtrack') + end + end + + describe "Checkout Order" do + it "shows no billing info notice" do + + fast_signin(user, '/client#/checkoutOrder') + + # the user should be toldy they have a empty cart + find('p.no-account-info-prompt') + end + + it "shows empty cart notice" do + + @recurlyClient.create_account(user, billing_info) + + fast_signin(user, '/client#/checkoutOrder') + + # the user should be toldy they have a empty cart + find('p.empty-cart-prompt') + find('.order-items-value.order-total', text:'-.--') + find('.order-items-value.sub-total', text:'-.--') + find('.order-items-value.taxes', text:'-.--') + find('.order-items-value.order-total', text:'-.--') + + # verify that both Place Your Orders are disabled + find('.order-content .place-order.disabled') + find('.order-panel .action-bar .place-order.disabled') + end + + it "shows one free item correctly" do + + ShoppingCart.add_jam_track_to_cart(user, jamtrack_acdc_backinblack) + + @recurlyClient.create_account(user, billing_info) + + user.reuse_card = true + user.has_redeemable_jamtrack = true + user.save! + + fast_signin(user, '/client#/checkoutOrder') + + find('p.order-prompt') + find('.order-items-value.order-total', text:'$0.00') + find('.order-items-value.shipping-handling', text:'$0.00') + find('.order-items-value.sub-total', text:'$0.00') + find('.order-items-value.taxes', text:'$0.00') + find('.order-items-value.grand-total', text:'$0.00') + end + + it "shows one free, one not free item correctly" do + ShoppingCart.add_jam_track_to_cart(user, jamtrack_acdc_backinblack) + user.reload + ShoppingCart.add_jam_track_to_cart(user, jamtrack_pearljam_evenflow) + + @recurlyClient.create_account(user, billing_info) + + user.reuse_card = true + user.has_redeemable_jamtrack = true + user.save! + + fast_signin(user, '/client#/checkoutOrder') + + find('p.order-prompt') + find('.order-items-value.order-total', text:'$1.99') + find('.order-items-value.shipping-handling', text:'$0.00') + find('.order-items-value.sub-total', text:'$1.99') + find('.order-items-value.taxes', text:'$0.00') + find('.order-items-value.grand-total', text:'$1.99') + end + + it "shows one non-free item correctly" do + user.has_redeemable_jamtrack = false + user.save! + + ShoppingCart.add_jam_track_to_cart(user, jamtrack_acdc_backinblack) + + @recurlyClient.create_account(user, billing_info) + + user.reuse_card = true + user.has_redeemable_jamtrack = true + user.save! + + fast_signin(user, '/client#/checkoutOrder') + + find('p.order-prompt') + find('.order-items-value.order-total', text:'$1.99') + find('.order-items-value.shipping-handling', text:'$0.00') + find('.order-items-value.sub-total', text:'$1.99') + find('.order-items-value.taxes', text:'$0.00') + find('.order-items-value.grand-total', text:'$1.99') + end + + it "shows two non-free items correctly" do + user.has_redeemable_jamtrack = false + user.save! + + ShoppingCart.add_jam_track_to_cart(user, jamtrack_acdc_backinblack) + user.reload + ShoppingCart.add_jam_track_to_cart(user, jamtrack_pearljam_evenflow) + + @recurlyClient.create_account(user, billing_info) + + user.reuse_card = true + user.has_redeemable_jamtrack = true + user.save! + + fast_signin(user, '/client#/checkoutOrder') + + find('p.order-prompt') + find('.order-items-value.order-total', text:'$3.98') + find('.order-items-value.shipping-handling', text:'$0.00') + find('.order-items-value.sub-total', text:'$3.98') + find('.order-items-value.taxes', text:'$0.00') + find('.order-items-value.grand-total', text:'$3.98') + + find('.place-order-center a.button-orange.place-order').trigger(:click) + + # and now we should see confirmation, and a notice that we are in a normal browser + find('.thanks-detail.jam-tracks-in-browser') + + acdc = jamtrack_acdc_backinblack.right_for_user(user) + acdc.redeemed.should be_false + pearljam = jamtrack_pearljam_evenflow.right_for_user(user) + pearljam.redeemed.should be_false + end + + it "shows purchase error correctly" do + + ShoppingCart.add_jam_track_to_cart(user, jamtrack_acdc_backinblack) + + @recurlyClient.create_account(user, billing_info) + + user.reuse_card = true + user.save! + + fast_signin(user, '/client#/checkoutOrder') + + find('p.order-prompt') + + # fiddle with the user's recurly-code so that the checkout fails + user.recurly_code = 'bleh' + user.save! + + find('.place-order-center a.button-orange.place-order').trigger(:click) + find('#order_error', text: "Error submitting payment: message: Couldn't find Account with account_code = bleh") + end + end + + + describe "Complete Checkout Flow" do + it "for anonymous user" do + visit "/client#/jamtrack" + find('h1', text: 'jamtracks') + #find('a', text: 'What is a JamTrack?') + + find("a.jamtrack-add-cart[data-jamtrack-id=\"#{jamtrack_acdc_backinblack.id}\"]").trigger(:click) + find('h1', text: 'shopping cart') + find('.cart-item-caption', text: "JamTrack: #{jamtrack_acdc_backinblack.name}") + find('.cart-item-price', text: "$ #{jamtrack_acdc_backinblack.price}") + + # attempt to checkout + find('a.button-orange', text: 'PROCEED TO CHECKOUT').trigger(:click) + + + # we should now be on checkoutSignin + find('h3', text: 'ALREADY A MEMBER OF THE JAMKAZAM COMMUNITY?') + verify_nav(1) + + # skip to payment without signing in + find('a.btnNext').trigger(:click) + + # this should take us to the payment screen + find('p.payment-prompt') + find('.jamkazam-account-signup') + + # fill out all billing info and account info + fill_in 'billing-first-name', with: 'Seth' + fill_in 'billing-last-name', with: 'Call' + fill_in 'billing-address1', with: '10704 Buckthorn Drive' + fill_in 'billing-city', with: 'Austin' + fill_in 'billing-state', with: 'Texas' + fill_in 'billing-zip', with: '78759' + fill_in 'card-number', with: '4111111111111111' + fill_in 'card-verify', with: '012' + fill_in 'checkout-signup-email', with: 'guy@jamkazam.com' + fill_in 'checkout-signup-password', with: 'jam123' + find('#divJamKazamTos ins.iCheck-helper').trigger(:click) # accept TOS + + # try to submit, and see order page + find('#payment-info-next').trigger(:click) + + # now see order page, and everything should appear free + find('p.order-prompt') + find('.order-items-value.order-total', text:'$0.00') + find('.order-items-value.shipping-handling', text:'$0.00') + find('.order-items-value.sub-total', text:'$0.00') + find('.order-items-value.taxes', text:'$0.00') + find('.order-items-value.grand-total', text:'$0.00') + + # click the ORDER button + find('.place-order-center a.button-orange.place-order').trigger(:click) + + # and now we should see confirmation, and a notice that we are in a normal browser + find('.thanks-detail.jam-tracks-in-browser') + + guy = User.find_by_email('guy@jamkazam.com') + jam_track_right = jamtrack_acdc_backinblack.right_for_user(guy) + # make sure it appears the user actually bought the jamtrack! + jam_track_right.should_not be_nil + jam_track_right.redeemed.should be_true + guy.has_redeemable_jamtrack.should be_false + + # now, go back to checkout flow again, and make sure we are told there are no free jam tracks + + visit "/client#/jamtrack" + + find("a.jamtrack-add-cart[data-jamtrack-id=\"#{jamtrack_pearljam_evenflow.id}\"]").trigger(:click) + find('h1', text: 'shopping cart') + find('.cart-item-caption', text: "JamTrack: #{jamtrack_pearljam_evenflow.name}") + find('.cart-item-price', text: "$ #{jamtrack_pearljam_evenflow.price}") + + # attempt to checkout + find('a.button-orange', text: 'PROCEED TO CHECKOUT').trigger(:click) + + # should be taken straight to order page + + # now see order page, and everything should appear free + find('p.order-prompt') + find('.order-items-value.order-total', text:'$1.99') + find('.order-items-value.shipping-handling', text:'$0.00') + find('.order-items-value.sub-total', text:'$1.99') + find('.order-items-value.taxes', text:'$0.00') + find('.order-items-value.grand-total', text:'$1.99') + + # click the ORDER button + find('.place-order-center a.button-orange.place-order').trigger(:click) + + # and now we should see confirmation, and a notice that we are in a normal browser + find('.thanks-detail.jam-tracks-in-browser') + + jam_track_right = jamtrack_pearljam_evenflow.right_for_user(guy) + # make sure it appears the user actually bought the jamtrack! + jam_track_right.should_not be_nil + jam_track_right.redeemed.should be_false + guy.has_redeemable_jamtrack.should be_false + + + end + + it "for existing user" do + fast_signin(user, "/client#/jamtrack") + find('h1', text: 'jamtracks') + #find('a', text: 'What is a JamTrack?') + + find("a.jamtrack-add-cart[data-jamtrack-id=\"#{jamtrack_acdc_backinblack.id}\"]").trigger(:click) + find('h1', text: 'shopping cart') + find('.cart-item-caption', text: "JamTrack: #{jamtrack_acdc_backinblack.name}") + find('.cart-item-price', text: "$ #{jamtrack_acdc_backinblack.price}") + + # attempt to checkout + find('a.button-orange', text: 'PROCEED TO CHECKOUT').trigger(:click) + + # we should be skipping the signin screen, and be taken directly to payment + + # this should take us to the payment screen + find('p.payment-prompt') + + # fill out all billing info and account info + fill_in 'billing-first-name', with: 'Seth' + fill_in 'billing-last-name', with: 'Call' + fill_in 'billing-address1', with: '10704 Buckthorn Drive' + fill_in 'billing-city', with: 'Austin' + fill_in 'billing-state', with: 'Texas' + fill_in 'billing-zip', with: '78759' + fill_in 'card-number', with: '4111111111111111' + fill_in 'card-verify', with: '012' + + # try to submit, and see order page + find('#payment-info-next').trigger(:click) + + # now see order page, and everything should appear free + find('p.order-prompt') + find('.order-items-value.order-total', text:'$0.00') + find('.order-items-value.shipping-handling', text:'$0.00') + find('.order-items-value.sub-total', text:'$0.00') + find('.order-items-value.taxes', text:'$0.00') + find('.order-items-value.grand-total', text:'$0.00') + + # click the ORDER button + find('.place-order-center a.button-orange.place-order').trigger(:click) + + # and now we should see confirmation, and a notice that we are in a normal browser + find('.thanks-detail.jam-tracks-in-browser') + + user.reload + jam_track_right = jamtrack_acdc_backinblack.right_for_user(user) + # make sure it appears the user actually bought the jamtrack! + jam_track_right.should_not be_nil + jam_track_right.redeemed.should be_true + user.has_redeemable_jamtrack.should be_false + end + end +end diff --git a/web/spec/features/download_spec.rb b/web/spec/features/download_spec.rb index 8c11fc157..7db9a6a9d 100644 --- a/web/spec/features/download_spec.rb +++ b/web/spec/features/download_spec.rb @@ -36,9 +36,6 @@ describe "User Progression", :js => true, :type => :feature, :capybara_feature end it "toggle active download" do - find(".download-others a[data-platform='Unix']").trigger(:click) - find("a.current-os-download")['data-platform'].should == "Unix" - find(".download-others a[data-platform='MacOSX']").trigger(:click) find("a.current-os-download")['data-platform'].should == "MacOSX" diff --git a/web/spec/features/in_session_spec.rb b/web/spec/features/in_session_spec.rb index ef96b1deb..a17f0d16a 100644 --- a/web/spec/features/in_session_spec.rb +++ b/web/spec/features/in_session_spec.rb @@ -28,7 +28,9 @@ describe "In a Session", :js => true, :type => :feature, :capybara_feature => tr sign_in_poltergeist finder visit "/client#/findSession" expect(page).to have_selector('#no-active-sessions') # verify private session is not found - sign_out_poltergeist(validate: true) + #sign_out_poltergeist(validate: true) + visit "/" + should_be_at_root end in_client(user) do set_session_access :public diff --git a/web/spec/features/jamtrack_shopping_spec.rb b/web/spec/features/jamtrack_shopping_spec.rb index 049f0101a..daeb34152 100644 --- a/web/spec/features/jamtrack_shopping_spec.rb +++ b/web/spec/features/jamtrack_shopping_spec.rb @@ -3,10 +3,10 @@ require 'spec_helper' describe "JamTrack Shopping", :js => true, :type => :feature, :capybara_feature => true do let(:user) { FactoryGirl.create(:user) } - let(:jt_us) { FactoryGirl.create(:jam_track, :name=>'jt_us', sales_region: 'United States', make_track: true) } - let(:jt_ww) { FactoryGirl.create(:jam_track, :name=>'jt_ww', sales_region: 'Worldwide', make_track: true) } - let(:jt_rock) { FactoryGirl.create(:jam_track, :name=>'jt_rock', genre: JamRuby::Genre.find('rock'), make_track: true) } - let(:jt_blues) { FactoryGirl.create(:jam_track, :name=>'jt_blues', genre: JamRuby::Genre.find('blues'), make_track: true) } + let(:jt_us) { FactoryGirl.create(:jam_track, :name=>'jt_us', sales_region: 'United States', make_track: true, original_artist: "foobar") } + let(:jt_ww) { FactoryGirl.create(:jam_track, :name=>'jt_ww', sales_region: 'Worldwide', make_track: true, original_artist: "barfoo") } + let(:jt_rock) { FactoryGirl.create(:jam_track, :name=>'jt_rock', genre: JamRuby::Genre.find('rock'), make_track: true, original_artist: "badfood") } + let(:jt_blues) { FactoryGirl.create(:jam_track, :name=>'jt_blues', genre: JamRuby::Genre.find('blues'), make_track: true, original_artist: "foodbart") } before(:all) do Capybara.javascript_driver = :poltergeist @@ -17,6 +17,7 @@ describe "JamTrack Shopping", :js => true, :type => :feature, :capybara_feature before(:each) do ShoppingCart.delete_all + JamTrackRight.delete_all JamTrack.delete_all JamTrackTrack.delete_all JamTrackLicensor.delete_all @@ -35,11 +36,11 @@ describe "JamTrack Shopping", :js => true, :type => :feature, :capybara_feature def find_jamtrack jamtrack, options = {} jamtrack_record = find(".jamtrack-record[jamtrack-id=\"#{jamtrack.id}\"]") jamtrack_record.find('.detail-value', text: jamtrack.name) - jamtrack_record.find('.detail-value', text: jamtrack.recording_type) + #jamtrack_record.find('.detail-value', text: jamtrack.recording_type) jamtrack_record.find('.detail-value', text: jamtrack.original_artist) - jamtrack_record.find('.detail-value', text: jamtrack.genre.description) - jamtrack_record.find('.detail-value', text: [jamtrack.songwriter, jamtrack.publisher].join(', ')) - jamtrack_record.find('.copyright-value', text: jamtrack.licensor.name) + # jamtrack_record.find('.detail-value', text: jamtrack.genre.description) + # jamtrack_record.find('.detail-value', text: [jamtrack.songwriter, jamtrack.publisher].join(', ')) + # jamtrack_record.find('.copyright-value', text: jamtrack.licensor.name) jamtrack_record.find('.jamtrack-price', text: "$ #{jamtrack.price}") if jamtrack.sales_region == 'United States' @@ -53,7 +54,7 @@ describe "JamTrack Shopping", :js => true, :type => :feature, :capybara_feature end if options[:added_cart] - jamtrack_record.find('a.jamtrack-add-cart-disabled', text: 'Purchased') + jamtrack_record.find('a.jamtrack-add-cart-disabled', text: 'Already In Cart') else jamtrack_record.find('a.jamtrack-add-cart.button-orange', text: 'Add to Cart') end @@ -67,8 +68,7 @@ describe "JamTrack Shopping", :js => true, :type => :feature, :capybara_feature before(:each) do visit "/client#/jamtrack" - find('h1', text: 'jamtracks') - find('a', text: 'What is a JamTrack?') + find('h1', text: 'jamtracks') jk_select('Any', '#jamtrack-find-form #jamtrack_availability') end @@ -86,11 +86,11 @@ describe "JamTrack Shopping", :js => true, :type => :feature, :capybara_feature not_find_jamtrack jt_rock end - it "filters with genre" do - jk_select('Blues', '#jamtrack-find-form #jamtrack_genre') - find_jamtrack jt_blues + it "filters with artist" do + jk_select("foobar", '#jamtrack-find-form #jamtrack_artist') + find_jamtrack jt_us + not_find_jamtrack jt_blues not_find_jamtrack jt_rock - not_find_jamtrack jt_us not_find_jamtrack jt_ww end @@ -103,12 +103,18 @@ describe "JamTrack Shopping", :js => true, :type => :feature, :capybara_feature end + it "sets artist filter" do + pending "The item is clearly present, so not currently sure why capybar can't find it" + visit "/client?artist=foobar#/jamtrack" + art = find('#jamtrack_artist') + puts "art: #{art}" + end + describe "Shopping Carts" do before(:each) do visit "/client#/jamtrack" - find('h1', text: 'jamtracks') - find('a', text: 'What is a JamTrack?') + find('h1', text: 'jamtracks') jk_select('Any', '#jamtrack-find-form #jamtrack_availability') end @@ -138,6 +144,5 @@ describe "JamTrack Shopping", :js => true, :type => :feature, :capybara_feature find('.shopping-sub-total', text: "Subtotal: $ #{jt_us.price + jt_ww.price}") end - end end diff --git a/web/spec/features/landing_spec.rb b/web/spec/features/landing_spec.rb index 1126ee01b..3e42e6e49 100644 --- a/web/spec/features/landing_spec.rb +++ b/web/spec/features/landing_spec.rb @@ -15,7 +15,7 @@ describe "Landing", :js => true, :type => :feature, :capybara_feature => true do UserMailer.deliveries.clear visit "/downloads" # this is just a good, generic page that uses the landing layout - find('div.tagline') + find('h2.create-account-header') end it "footer links work" do diff --git a/web/spec/features/launch_app_spec.rb b/web/spec/features/launch_app_spec.rb index b759aea49..dbf8bf199 100644 --- a/web/spec/features/launch_app_spec.rb +++ b/web/spec/features/launch_app_spec.rb @@ -28,7 +28,7 @@ describe "Launch App", :js => true, :type => :feature, :capybara_feature => true end find('a.btn-go-to-download-page').trigger(:click) - find('h3', text: 'SYSTEM REQUIREMENTS:') + find('h2.create-account-header', text: 'Download the free JamKazam app') end end diff --git a/web/spec/features/profile_menu_spec.rb b/web/spec/features/profile_menu_spec.rb index 68ea06c8b..c70240b4f 100644 --- a/web/spec/features/profile_menu_spec.rb +++ b/web/spec/features/profile_menu_spec.rb @@ -53,7 +53,7 @@ describe "Profile Menu", :js => true, :type => :feature, :capybara_feature => tr click_link 'Sign Out' end - it { should_be_at_root } + it { should_be_at_logged_out_client } end describe "Download App link" do @@ -67,7 +67,7 @@ describe "Profile Menu", :js => true, :type => :feature, :capybara_feature => tr if pops_external page.driver.window_handles.last page.within_window page.driver.window_handles.last do - should have_content('Here are a few of the great things you can do with JamKazam:') + should have_content('JamTracks are multi-track pro recordings you can use to:') end else should have_selector("a.current-os-download") @@ -103,17 +103,14 @@ describe "Profile Menu", :js => true, :type => :feature, :capybara_feature => tr it_behaves_like "Profile Menu Assertions", true end - describe "Profile Menu while in Web" do + describe "Profile Menu while in Web (hidden)" do before(:each) do UserMailer.deliveries.clear fast_signin(user, "/downloads") - find('div.tagline') - # open menu - find('.userinfo').hover - find('a', text: 'Sign Out') + find('h2.create-account-header') + # it's not in /downloads anymore + expect(page).to_not have_selector('.userinfo') end - - it_behaves_like "Profile Menu Assertions", false end diff --git a/web/spec/features/signin_spec.rb b/web/spec/features/signin_spec.rb index 689b8251b..ad4f7dfec 100644 --- a/web/spec/features/signin_spec.rb +++ b/web/spec/features/signin_spec.rb @@ -175,7 +175,7 @@ describe "signin" do click_button "SIGN IN" end - find('h1', text: 'Play music together over the Internet as if in the same room') + should_be_at_logged_out_client end # if a cookie with the default domain is found with another, delete the one with the default domain @@ -201,7 +201,7 @@ describe "signin" do click_button "SIGN IN" end - find('h1', text: 'Play music together over the Internet as if in the same room') + should_be_at_logged_out_client delete_called.should be_true end @@ -211,6 +211,23 @@ describe "signin" do sign_in_poltergeist(user) sign_out_poltergeist + + wait_until_curtain_gone + + # musicians homecard should be disabled + find('.homecard.musicians.not-logged-in').trigger(:click) + find('h1', text: 'Login Required') + find('.btnClose').trigger(:click) + + # profile homecard should be disabled (this one is handled in homeScreen.js instead of in layout.js) + find('.homecard.profile.not-logged-in').trigger(:click) + find('h1', text: 'Login Required') + find('.btnClose').trigger(:click) + + # sidebar should be disabled + find('[layout-id="panelSearch"] [layout-panel="expanded"] [layout-panel="header"]').trigger(:click) + find('h1', text: 'Login Required') + find('.btnClose').trigger(:click) end diff --git a/web/spec/features/signup_spec.rb b/web/spec/features/signup_spec.rb index bc37f1567..126a116a2 100644 --- a/web/spec/features/signup_spec.rb +++ b/web/spec/features/signup_spec.rb @@ -23,7 +23,7 @@ describe "Signup", :js => true, :type => :feature, :capybara_feature => true do before { visit signup_path } it "should initialize successfully" do - should have_selector('h2', text: 'Create a JamKazam account') + should have_selector('h2.create-account-header', text: '1Create your free JamKazam account') # we should see these locations in the signup form already chosen location = GeoIpLocations.lookup('127.0.0.1') @@ -47,7 +47,7 @@ describe "Signup", :js => true, :type => :feature, :capybara_feature => true do # Successful signup with no invitation tells you to go sign up it { should have_title("JamKazam | Congratulations") - should have_content("You have successfully registered as a JamKazam musician.") + should have_content("Congratulations! Your account is ready.") user = User.find_by_email('newuser1@jamkazam.com') user.musician_instruments.length.should == 1 location = GeoIpLocations.lookup('127.0.0.1') @@ -92,8 +92,7 @@ describe "Signup", :js => true, :type => :feature, :capybara_feature => true do # Successful signup with no invitation tells you to go sign up it { should have_title("JamKazam | Congratulations") - should have_selector('div.tagline', text: "Congratulations!") - should have_selector('a.button-orange.m0', text: 'PROCEED TO JAMKAZAM SITE') + should have_selector('.flash-content', text: "Congratulations! Your account is ready.") User.find_by_email('somefan@jamkazam.com').musician_instruments.length.should == 0 # an email is sent on no-invite signup UserMailer.deliveries.length.should == 1 @@ -124,7 +123,7 @@ describe "Signup", :js => true, :type => :feature, :capybara_feature => true do # Successful sign-in goes to the client it { should have_title("JamKazam") - should have_selector('div.tagline', text: "Congratulations!") + should have_selector('.flash-content', text: "Congratulations! Your account is ready.") UserMailer.deliveries.length.should == 1 uri = URI.parse(current_url) "#{uri.path}?#{uri.query}".should == congratulations_musician_path(:type => 'Native') @@ -152,7 +151,7 @@ describe "Signup", :js => true, :type => :feature, :capybara_feature => true do # Successful sign-in goes to the client it { should have_title("JamKazam") - should have_selector('div.tagline', text: "Congratulations!") + should have_selector('.flash-content', text: "Congratulations! Your account is ready.") @user.friends?(User.find_by_email("newuser3@jamkazam.com")) User.find_by_email("newuser3@jamkazam.com").friends?(@user) uri = URI.parse(current_url) @@ -176,7 +175,7 @@ describe "Signup", :js => true, :type => :feature, :capybara_feature => true do check("jam_ruby_user[terms_of_service]") click_button "CREATE ACCOUNT" page.should have_title("JamKazam") - should have_selector('div.tagline', text: "Congratulations!") + should have_selector('.flash-content', text: "Congratulations! Your account is ready.") sign_out visit "#{signup_path}?invitation_code=#{@invited_user.invitation_code}" end @@ -204,7 +203,7 @@ describe "Signup", :js => true, :type => :feature, :capybara_feature => true do it "success" do page.should have_title("JamKazam") - should have_selector('div.tagline', text: "Congratulations!") + should have_selector('.flash-content', text: "Congratulations! Your account is ready.") uri = URI.parse(current_url) "#{uri.path}?#{uri.query}".should == congratulations_musician_path(:type => 'Facebook') end @@ -227,7 +226,7 @@ describe "Signup", :js => true, :type => :feature, :capybara_feature => true do def signup_good should have_title("JamKazam") - should have_selector('div.tagline', text: "Congratulations!") + should have_selector('.flash-content', text: "Congratulations! Your account is ready.") @user.friends?(User.find_by_email(@invited_user_email)) User.find_by_email(@invited_user_email).friends?(@user) uri = URI.parse(current_url) @@ -272,7 +271,7 @@ describe "Signup", :js => true, :type => :feature, :capybara_feature => true do it { should have_title("JamKazam | Congratulations") - should have_content("You have successfully registered as a JamKazam musician.") + should have_selector('.flash-content', text: "Congratulations! Your account is ready.") User.find_by_email('newuser5@jamkazam.com').musician_instruments.length.should == 1 User.find_by_email('what@jamkazam.com').should be_nil # an email is sent when you invite but use a different email than the one used to invite diff --git a/web/spec/features/user_progression_spec.rb b/web/spec/features/user_progression_spec.rb index 2d46a0160..96ca494be 100644 --- a/web/spec/features/user_progression_spec.rb +++ b/web/spec/features/user_progression_spec.rb @@ -41,7 +41,7 @@ describe "User Progression", :js => true, :type => :feature, :capybara_feature check("jam_ruby_user[terms_of_service]") click_button "CREATE ACCOUNT" - should have_content("You have successfully registered as a JamKazam musician.") + should have_content("Congratulations! Your account is ready.") detected_os = find("a.current-os-download")['data-platform'] if detected_os != "Win32" find(".download-others a[data-platform='Win32']").trigger(:click) diff --git a/web/spec/features/welcome_spec.rb b/web/spec/features/welcome_spec.rb index a83ec1044..4a2cd9e8f 100644 --- a/web/spec/features/welcome_spec.rb +++ b/web/spec/features/welcome_spec.rb @@ -160,7 +160,7 @@ describe "Welcome", :js => true, :type => :feature, :capybara_feature => true d it "click will redirect to signup page" do find('.signup-email').trigger(:click) - find('h2', text: 'Create a JamKazam account') + find('h2.create-account-header', text: '1Create your free JamKazam account') end end @@ -173,7 +173,7 @@ describe "Welcome", :js => true, :type => :feature, :capybara_feature => true d it "click will redirect to facebook for authorization" do find('.signup-facebook').trigger(:click) - find('h2', text: 'Create a JamKazam account') + find('h2.create-account-header', text: '1Create your free JamKazam account') find_field('jam_ruby_user[first_name]').value.should eq 'John' find_field('jam_ruby_user[last_name]').value.should eq 'Doe' find_field('jam_ruby_user[email]').value.should eq 'facebook@jamkazam.com' diff --git a/web/spec/support/app_config.rb b/web/spec/support/app_config.rb index 0a7c63cae..3287dd0be 100644 --- a/web/spec/support/app_config.rb +++ b/web/spec/support/app_config.rb @@ -78,6 +78,10 @@ def web_config def signing_job_queue_max_time 20 # 20 seconds end + + def one_free_jamtrack_per_user + true + end end klass.new end diff --git a/web/spec/support/utilities.rb b/web/spec/support/utilities.rb index 8982fd5a1..7a89d9b23 100644 --- a/web/spec/support/utilities.rb +++ b/web/spec/support/utilities.rb @@ -200,7 +200,7 @@ end def sign_out_poltergeist(options = {}) open_user_dropdown click_link 'Sign Out' - should_be_at_signin if options[:validate] + should_be_at_logged_out_client if options[:validate] end def open_user_dropdown @@ -221,6 +221,11 @@ def should_be_at_signin find('h1', text: 'sign in or register') end +def should_be_at_logged_out_client + find('#profile a.signin', text: 'Sign Up') + find('.musicians.not-logged-in') +end + def leave_music_session_sleep_delay # add a buffer to ensure WSG has enough time to expire sleep_dur = (Rails.application.config.websocket_gateway_connect_time_stale_browser + diff --git a/websocket-gateway/spec/factories.rb b/websocket-gateway/spec/factories.rb index a32281fbb..ed446a48f 100644 --- a/websocket-gateway/spec/factories.rb +++ b/websocket-gateway/spec/factories.rb @@ -11,6 +11,7 @@ FactoryGirl.define do state "NC" country "US" terms_of_service true + reuse_card true factory :admin do