Merge with latest from develop and manually fix.

This commit is contained in:
Steven Miers 2015-05-18 16:02:07 -05:00
commit d684ecda3b
142 changed files with 2940 additions and 745 deletions

View File

@ -32,6 +32,7 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do
row :birth_date row :birth_date
row :gender row :gender
row :email_confirmed row :email_confirmed
row :remember_token
row :image do user.photo_url ? image_tag(user.photo_url) : '' end row :image do user.photo_url ? image_tag(user.photo_url) : '' end
end end
active_admin_comments active_admin_comments

View File

@ -290,3 +290,4 @@ jam_track_right_private_key.sql
first_downloaded_jamtrack_at.sql first_downloaded_jamtrack_at.sql
signing.sql signing.sql
enhance_band_profile.sql enhance_band_profile.sql
optimized_redeemption.sql

View File

@ -0,0 +1,13 @@
CREATE TABLE machine_fingerprints (
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id VARCHAR(64) NOT NULL REFERENCES users(id) ON DELETE CASCADE,
fingerprint VARCHAR(20000) NOT NULL UNIQUE,
when_taken VARCHAR NOT NULL,
print_type VARCHAR NOT NULL,
remote_ip VARCHAR(1000) NOT NULL,
jam_track_right_id BIGINT REFERENCES jam_track_rights(id) ON DELETE SET NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
ALTER TABLE jam_track_rights ADD COLUMN redeemed_and_fingerprinted BOOLEAN DEFAULT FALSE;

View File

@ -103,6 +103,7 @@ require "jam_ruby/models/genre"
require "jam_ruby/models/user" require "jam_ruby/models/user"
require "jam_ruby/models/anonymous_user" require "jam_ruby/models/anonymous_user"
require "jam_ruby/models/signup_hint" require "jam_ruby/models/signup_hint"
require "jam_ruby/models/machine_fingerprint"
require "jam_ruby/models/rsvp_request" require "jam_ruby/models/rsvp_request"
require "jam_ruby/models/rsvp_slot" require "jam_ruby/models/rsvp_slot"
require "jam_ruby/models/rsvp_request_rsvp_slot" require "jam_ruby/models/rsvp_request_rsvp_slot"

View File

@ -52,7 +52,7 @@ module JamRuby
def generate_signup_url(invited_user) def generate_signup_url(invited_user)
invited_user.generate_signup_url invited_user.generate_signup_url
# "http://www.jamkazam.com/signup?invitation_code=#{invited_user.invitation_code}" # "https://www.jamkazam.com/signup?invitation_code=#{invited_user.invitation_code}"
end end
end end
end end

View File

@ -8,7 +8,7 @@
<br/> <br/>
<br/> <br/>
<p> <p>
This email was received because someone left feedback at <a style="color: #ffcc00;" href="http://www.jamkazam.com/corp/contact">http://www.jamkazam.com/corp/contact</a> This email was received because someone left feedback at <a style="color: #ffcc00;" href="https://www.jamkazam.com/corp/contact">http://www.jamkazam.com/corp/contact</a>
</p> </p>
</body> </body>
</html> </html>

View File

@ -5,4 +5,4 @@ From <%= @email %>:
<%= @body %> <%= @body %>
This email was received because someone left feedback at http://www.jamkazam.com/corp/contact This email was received because someone left feedback at https://www.jamkazam.com/corp/contact

View File

@ -7,7 +7,7 @@
</p> </p>
<p> <p>
<a style="color: #ffcc00;" href="http://www.jamkazam.com/downloads">Go to Download Page</a> <a style="color: #ffcc00;" href="https://www.jamkazam.com/downloads">Go to Download Page</a>
</p> </p>
<p> <p>

View File

@ -4,7 +4,7 @@ Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
We noticed that you have registered as a JamKazam musician, but you have not yet downloaded and started using the free JamKazam application. You can find other musicians and listen to sessions and recordings on our website, but you need the free JamKazam application to play with other musicians online. Please click the link below to go to the download page for the free JamKazam application, or visit our JamKazam support center so that we can help you get up and running. We noticed that you have registered as a JamKazam musician, but you have not yet downloaded and started using the free JamKazam application. You can find other musicians and listen to sessions and recordings on our website, but you need the free JamKazam application to play with other musicians online. Please click the link below to go to the download page for the free JamKazam application, or visit our JamKazam support center so that we can help you get up and running.
Go to Download Page: http://www.jamkazam.com/downloads Go to Download Page: https://www.jamkazam.com/downloads
Go to Support Center: https://jamkazam.desk.com Go to Support Center: https://jamkazam.desk.com

View File

@ -10,7 +10,7 @@
Its still very early in our companys development, so we dont have zillions of users online on our service yet. If you click Find Session, you will often not find a good session to join, both due to the number of musicians online at any given time, and also because you wont see private sessions where groups of musicians dont want to be interrupted in their sessions. Its still very early in our companys development, so we dont have zillions of users online on our service yet. If you click Find Session, you will often not find a good session to join, both due to the number of musicians online at any given time, and also because you wont see private sessions where groups of musicians dont want to be interrupted in their sessions.
</p> </p>
<p>If you are having trouble getting into sessions, wed suggest you click the Musicians tile on the home screen of the app or the website: <a style="color: #ffcc00;" href="http://www.jamkazam.com/client#/musicians">Go To Musicians Page</a> <p>If you are having trouble getting into sessions, wed suggest you click the Musicians tile on the home screen of the app or the website: <a style="color: #ffcc00;" href="https://www.jamkazam.com/client#/musicians">Go To Musicians Page</a>
</p> </p>
<p>This will display the JamKazam musicians sorted by latency to you - in other words, you can see which musicians have good network connections to you. Any musicians with green and yellow latency scores have good enough connections to support a play session with you. We recommend that read the profiles of these musicians to find others with shared musical interests and good network connections to you, and then use the Message button to say hi and see if they are interested in playing with you. If they are, use the Connect button to “friend” them on JamKazam, and use the Message button to set up a time to meet online for a session. <p>This will display the JamKazam musicians sorted by latency to you - in other words, you can see which musicians have good network connections to you. Any musicians with green and yellow latency scores have good enough connections to support a play session with you. We recommend that read the profiles of these musicians to find others with shared musical interests and good network connections to you, and then use the Message button to say hi and see if they are interested in playing with you. If they are, use the Connect button to “friend” them on JamKazam, and use the Message button to set up a time to meet online for a session.

View File

@ -7,7 +7,7 @@ We noticed that you havent yet played in a JamKazam session with multiple mus
Find Other Musicians on JamKazam Find Other Musicians on JamKazam
Its still very early in our companys development, so we dont have zillions of users online on our service yet. If you click Find Session, you will often not find a good session to join, both due to the number of musicians online at any given time, and also because you wont see private sessions where groups of musicians dont want to be interrupted in their sessions. Its still very early in our companys development, so we dont have zillions of users online on our service yet. If you click Find Session, you will often not find a good session to join, both due to the number of musicians online at any given time, and also because you wont see private sessions where groups of musicians dont want to be interrupted in their sessions.
If you are having trouble getting into sessions, wed suggest you click the Musicians tile on the home screen of the app or the website: http://www.jamkazam.com/client#/musicians If you are having trouble getting into sessions, wed suggest you click the Musicians tile on the home screen of the app or the website: https://www.jamkazam.com/client#/musicians
This will display the JamKazam musicians sorted by latency to you - in other words, you can see which musicians have good network connections to you. Any musicians with green and yellow latency scores have good enough connections to support a play session with you. We recommend that read the profiles of these musicians to find others with shared musical interests and good network connections to you, and then use the Message button to say hi and see if they are interested in playing with you. If they are, use the Connect button to “friend” them on JamKazam, and use the Message button to set up a time to meet online for a session. This will display the JamKazam musicians sorted by latency to you - in other words, you can see which musicians have good network connections to you. Any musicians with green and yellow latency scores have good enough connections to support a play session with you. We recommend that read the profiles of these musicians to find others with shared musical interests and good network connections to you, and then use the Message button to say hi and see if they are interested in playing with you. If they are, use the Connect button to “friend” them on JamKazam, and use the Message button to set up a time to meet online for a session.

View File

@ -7,7 +7,7 @@
</p> </p>
<p>Find Other Musicians on JamKazam<br /> <p>Find Other Musicians on JamKazam<br />
To find and connect with other musicians who are already on JamKazam, wed suggest you click the Musicians tile on the home screen of the app or the website: <a style="color: #ffcc00;" href="http://www.jamkazam.com/client#/musicians">Go To Musicians Page</a> To find and connect with other musicians who are already on JamKazam, wed suggest you click the Musicians tile on the home screen of the app or the website: <a style="color: #ffcc00;" href="https://www.jamkazam.com/client#/musicians">Go To Musicians Page</a>
</p> </p>
<p>This will display the JamKazam musicians sorted by latency to you - in other words, you can see which musicians have good network connections to you. Any musicians with green and yellow latency scores have good enough connections to support a play session with you. We recommend that you read the profiles of these musicians to find others with shared musical interests and good network connections to you, and then use the Message button to say hi and see if they are interested in playing with you. If they are, use the Connect button to “friend” them on JamKazam, and use the Message button to set up a time to meet online for a session. <p>This will display the JamKazam musicians sorted by latency to you - in other words, you can see which musicians have good network connections to you. Any musicians with green and yellow latency scores have good enough connections to support a play session with you. We recommend that you read the profiles of these musicians to find others with shared musical interests and good network connections to you, and then use the Message button to say hi and see if they are interested in playing with you. If they are, use the Connect button to “friend” them on JamKazam, and use the Message button to set up a time to meet online for a session.

View File

@ -5,7 +5,7 @@ Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
We noticed that you havent yet connected with any friends on JamKazam. Connecting with friends is the best way to help you get into sessions with other musicians on JamKazam. Here are a couple of good ways to connect with others. We noticed that you havent yet connected with any friends on JamKazam. Connecting with friends is the best way to help you get into sessions with other musicians on JamKazam. Here are a couple of good ways to connect with others.
Find Other Musicians on JamKazam Find Other Musicians on JamKazam
To find and connect with other musicians who are already on JamKazam, wed suggest you click the Musicians tile on the home screen of the app or the website: http://www.jamkazam.com/client#/musicians To find and connect with other musicians who are already on JamKazam, wed suggest you click the Musicians tile on the home screen of the app or the website: https://www.jamkazam.com/client#/musicians
This will display the JamKazam musicians sorted by latency to you - in other words, you can see which musicians have good network connections to you. Any musicians with green and yellow latency scores have good enough connections to support a play session with you. We recommend that you read the profiles of these musicians to find others with shared musical interests and good network connections to you, and then use the Message button to say hi and see if they are interested in playing with you. If they are, use the Connect button to “friend” them on JamKazam, and use the Message button to set up a time to meet online for a session. This will display the JamKazam musicians sorted by latency to you - in other words, you can see which musicians have good network connections to you. Any musicians with green and yellow latency scores have good enough connections to support a play session with you. We recommend that you read the profiles of these musicians to find others with shared musical interests and good network connections to you, and then use the Message button to say hi and see if they are interested in playing with you. If they are, use the Connect button to “friend” them on JamKazam, and use the Message button to set up a time to meet online for a session.

View File

@ -7,7 +7,7 @@
</p> </p>
<% [:twitter, :facebook, :google].each do |site| %> <% [:twitter, :facebook, :google].each do |site| %>
<%= link_to(image_tag("http://www.jamkazam.com/assets/content/icon_#{site}.png", :style => "vertical-align:top"), "http://www.jamkazam.com/endorse/@USERID/#{site}?src=email") %>&nbsp; <%= link_to(image_tag("https://www.jamkazam.com/assets/content/icon_#{site}.png", :style => "vertical-align:top"), "https://www.jamkazam.com/endorse/@USERID/#{site}?src=email") %>&nbsp;
<% end %> <% end %>
<p>-- Team JamKazam <p>-- Team JamKazam

View File

@ -5,7 +5,7 @@ Hello <%= EmailBatchProgression::VAR_FIRST_NAME %> --
JamKazam is a young company/service built through the sweat and commitment of a small group of music-loving techies. Please help us continue to grow the service and attract more musicians to play online by liking and/or following us on Facebook, Twitter, and Google+. Just click the icons below to give us little push, thanks! JamKazam is a young company/service built through the sweat and commitment of a small group of music-loving techies. Please help us continue to grow the service and attract more musicians to play online by liking and/or following us on Facebook, Twitter, and Google+. Just click the icons below to give us little push, thanks!
<% [:twitter, :facebook, :google].each do |site| %> <% [:twitter, :facebook, :google].each do |site| %>
http://www.jamkazam.com/endorse/@USERID/#{site}?src=email https://www.jamkazam.com/endorse/@USERID/#{site}?src=email
<% end %> <% end %>

View File

@ -24,7 +24,7 @@ Hi <%= @user.first_name %>,
<% end %> <% end %>
</table> </table>
</p> </p>
<p>There are currently <%= @new_musicians.size%> musicians on JamKazam with low enough latency Internet connections to you to support a good online session. To see ALL the JamKazam musicians with whom you may want to connect and play, view our Musicians page at: <a style="color: #ffcc00;" href="http://www.jamkazam.com/client#/musicians">http://www.jamkazam.com/client#/musicians</a>. <p>There are currently <%= @new_musicians.size%> musicians on JamKazam with low enough latency Internet connections to you to support a good online session. To see ALL the JamKazam musicians with whom you may want to connect and play, view our Musicians page at: <a style="color: #ffcc00;" href="https://www.jamkazam.com/client#/musicians">http://www.jamkazam.com/client#/musicians</a>.
</p> </p>
<p>Best Regards,</p> <p>Best Regards,</p>

View File

@ -11,7 +11,7 @@ The following new musicians have joined JamKazam within the last week, and have
<%= user.biography %> <%= user.biography %>
<% end %> <% end %>
There are currently <%= @new_musicians.size%> musicians on JamKazam with low enough latency Internet connections to you to support a good online session. To see ALL the JamKazam musicians with whom you may want to connect and play, view our Musicians page at: http://www.jamkazam.com/client#/musicians. There are currently <%= @new_musicians.size%> musicians on JamKazam with low enough latency Internet connections to you to support a good online session. To see ALL the JamKazam musicians with whom you may want to connect and play, view our Musicians page at: https://www.jamkazam.com/client#/musicians.
Best Regards, Best Regards,
Team JamKazam Team JamKazam

View File

@ -68,16 +68,16 @@
<td><%= sess.genre.description %></td> <td><%= sess.genre.description %></td>
<td> <td>
<%= sess.name %><br/> <%= sess.name %><br/>
<a style="color: #ffcc00;" href="<%= "http://www.jamkazam.com/sessions/#{sess.id}/details" %>">Details</a> <a style="color: #ffcc00;" href="<%= "https://www.jamkazam.com/sessions/#{sess.id}/details" %>">Details</a>
</td> </td>
<td><%= sess.description %></td> <td><%= sess.description %></td>
<td style="text-align:center"> <td style="text-align:center">
<span class="latency"> <span class="latency">
<span class="latency-value"><%= (sess.latency / 2).round %> ms</span> <span class="latency-value"><%= (sess.latency / 2).round %> ms</span>
<% if sess.latency <= (APP_CONFIG.max_good_full_score / 2) %> <% if sess.latency <= (APP_CONFIG.max_good_full_score / 2) %>
<%= image_tag("http://www.jamkazam.com/assets/content/icon_green_score.png", alt: 'good score icon') %> <%= image_tag("https://www.jamkazam.com/assets/content/icon_green_score.png", alt: 'good score icon') %>
<% else %> <% else %>
<%= image_tag("http://www.jamkazam.com/assets/content/icon_yellow_score.png", alt: 'fair score icon') %> <%= image_tag("https://www.jamkazam.com/assets/content/icon_yellow_score.png", alt: 'fair score icon') %>
<% end %> <% end %>
</span> </span>
</td> </td>
@ -86,7 +86,7 @@
</tbody> </tbody>
</table> </table>
<p>To see ALL the scheduled sessions that you might be interested in joining, view our <a style="color: #ffcc00;" href="http://www.jamkazam.com/client#/findSession">Find Session page</a>.</p> <p>To see ALL the scheduled sessions that you might be interested in joining, view our <a style="color: #ffcc00;" href="https://www.jamkazam.com/client#/findSession">Find Session page</a>.</p>
<p>Best Regards,</p> <p>Best Regards,</p>

View File

@ -9,7 +9,7 @@ GENRE | NAME | DESCRIPTION | LATENCY
<%= sess.genre.description %> | <%= sess.name %> | <%= sess.description %> | <%= sess.latency %> ms <%= sess.genre.description %> | <%= sess.name %> | <%= sess.description %> | <%= sess.latency %> ms
<% end %> <% end %>
To see ALL the scheduled sessions that you might be interested in joining, view our Find Session page at: http://www.jamkazam.com/client#/findSession. To see ALL the scheduled sessions that you might be interested in joining, view our Find Session page at: https://www.jamkazam.com/client#/findSession.
Best Regards, Best Regards,

View File

@ -19,7 +19,7 @@
<body bgcolor="#000000" style="margin-top:10px;font-family:Arial, Helvetica, sans-serif;"> <body bgcolor="#000000" style="margin-top:10px;font-family:Arial, Helvetica, sans-serif;">
<table bgcolor="#262626" width="650" align="center" cellpadding="0" cellspacing="0"> <table bgcolor="#262626" width="650" align="center" cellpadding="0" cellspacing="0">
<tr> <tr>
<td><img src="http://www.jamkazam.com/assets/email/header.png" width="650" height="183" alt="JamKazam"></td> <td><img src="https://www.jamkazam.com/assets/email/header.png" width="650" height="183" alt="JamKazam"></td>
</tr> </tr>
</table> </table>
<table bgcolor="#262626" width="650" align="center" cellpadding="30" cellspacing="0"> <table bgcolor="#262626" width="650" align="center" cellpadding="30" cellspacing="0">
@ -51,7 +51,7 @@
<!-- CALL OUT BOX --> <!-- CALL OUT BOX -->
</font></p> </font></p>
<p style="margin-top:0px"><font size="2" color="#7FACBA" face="Arial, Helvetica, sans-serif">This email was sent to you because you have an account at <a style="color: #ffcc00;" href="http://www.jamkazam.com">JamKazam</a>. <p style="margin-top:0px"><font size="2" color="#7FACBA" face="Arial, Helvetica, sans-serif">This email was sent to you because you have an account at <a style="color: #ffcc00;" href="https://www.jamkazam.com">JamKazam</a>.
</td></tr></table> </td></tr></table>
</td> </td>

View File

@ -2,7 +2,7 @@
<% unless @suppress_user_has_account_footer == true %> <% unless @suppress_user_has_account_footer == true %>
This email was sent to you because you have an account at JamKazam / http://www.jamkazam.com. This email was sent to you because you have an account at JamKazam / https://www.jamkazam.com.
<% end %> <% end %>
Copyright <%= Time.now.year %> JamKazam, Inc. All rights reserved. Copyright <%= Time.now.year %> JamKazam, Inc. All rights reserved.

View File

@ -5,7 +5,7 @@
<% end %> <% end %>
<% unless @user.nil? || @suppress_user_has_account_footer == true %> <% unless @user.nil? || @suppress_user_has_account_footer == true %>
This email was sent to you because you have an account at JamKazam / http://www.jamkazam.com. To unsubscribe: http://www.jamkazam.com/unsubscribe/<%=@user.unsubscribe_token%>. This email was sent to you because you have an account at JamKazam / https://www.jamkazam.com. To unsubscribe: https://www.jamkazam.com/unsubscribe/<%=@user.unsubscribe_token%>.
<% end %> <% end %>
Copyright <%= Time.now.year %> JamKazam, Inc. All rights reserved. Copyright <%= Time.now.year %> JamKazam, Inc. All rights reserved.

View File

@ -774,6 +774,7 @@ module JamRuby
self.opening_jam_track = true self.opening_jam_track = true
self.save self.save
self.opening_jam_track = false self.opening_jam_track = false
#self.tick_track_changes
end end
def close_jam_track def close_jam_track

View File

@ -4,10 +4,11 @@
module JamRuby module JamRuby
class AnonymousUser class AnonymousUser
attr_accessor :id attr_accessor :id, :cookies
def initialize(id) def initialize(id, cookies)
@id = id @id = id
@cookies = cookies
end end
def shopping_carts def shopping_carts
@ -23,7 +24,11 @@ module JamRuby
end end
def has_redeemable_jamtrack def has_redeemable_jamtrack
APP_CONFIG.one_free_jamtrack_per_user APP_CONFIG.one_free_jamtrack_per_user && !@cookies[:redeemed_jamtrack]
end
def signup_hint
SignupHint.find_by_anonymous_user_id(@id)
end end
end end
end end

View File

@ -54,7 +54,7 @@ module JamRuby
mount.source_pass = APP_CONFIG.icecast_hardcoded_source_password || SecureRandom.urlsafe_base64 mount.source_pass = APP_CONFIG.icecast_hardcoded_source_password || SecureRandom.urlsafe_base64
mount.stream_name = "JamKazam music session created by #{music_session.creator.name}" mount.stream_name = "JamKazam music session created by #{music_session.creator.name}"
mount.stream_description = music_session.description mount.stream_description = music_session.description
mount.stream_url = "http://www.jamkazam.com" ## TODO/XXX, the jamkazam url should be the page hosting the widget mount.stream_url = "https://www.jamkazam.com" ## TODO/XXX, the jamkazam url should be the page hosting the widget
mount.genre = music_session.genre.description mount.genre = music_session.genre.description
mount mount
end end

View File

@ -3,21 +3,24 @@ module JamRuby
# describes what users have rights to which tracks # describes what users have rights to which tracks
class JamTrackRight < ActiveRecord::Base class JamTrackRight < ActiveRecord::Base
include JamRuby::S3ManagerMixin include JamRuby::S3ManagerMixin
attr_accessible :user, :jam_track, :user_id, :jam_track_id, :download_count
@@log = Logging.logger[JamTrackRight]
attr_accessible :user, :jam_track, :user_id, :jam_track_id, :download_count
attr_accessible :user_id, :jam_track_id, as: :admin attr_accessible :user_id, :jam_track_id, as: :admin
attr_accessible :url_48, :md5_48, :length_48, :url_44, :md5_44, :length_44 attr_accessible :url_48, :md5_48, :length_48, :url_44, :md5_44, :length_44
belongs_to :user, class_name: "JamRuby::User" # the owner, or purchaser of the jam_track belongs_to :user, class_name: "JamRuby::User" # the owner, or purchaser of the jam_track
belongs_to :jam_track, class_name: "JamRuby::JamTrack" belongs_to :jam_track, class_name: "JamRuby::JamTrack"
validates :user, presence:true validates :user, presence: true
validates :jam_track, presence:true validates :jam_track, presence: true
validates :is_test_purchase, inclusion: {in: [true, false]} validates :is_test_purchase, inclusion: {in: [true, false]}
validate :verify_download_count validate :verify_download_count
after_save :after_save after_save :after_save
validates_uniqueness_of :user_id, scope: :jam_track_id validates_uniqueness_of :user_id, scope: :jam_track_id
# Uploads the JKZ: # Uploads the JKZ:
mount_uploader :url_48, JamTrackRightUploader mount_uploader :url_48, JamTrackRightUploader
mount_uploader :url_44, JamTrackRightUploader mount_uploader :url_44, JamTrackRightUploader
@ -42,7 +45,7 @@ module JamRuby
def filename(bitrate) def filename(bitrate)
"#{jam_track.name}-#{bitrate == :url_48 ? '48' : '44'}.jkz" "#{jam_track.name}-#{bitrate == :url_48 ? '48' : '44'}.jkz"
end end
def verify_download_count def verify_download_count
if (self.download_count < 0 || self.download_count > MAX_JAM_TRACK_DOWNLOADS) && !@current_user.admin if (self.download_count < 0 || self.download_count > MAX_JAM_TRACK_DOWNLOADS) && !@current_user.admin
errors.add(:download_count, "must be less than or equal to #{MAX_JAM_TRACK_DOWNLOADS}") errors.add(:download_count, "must be less than or equal to #{MAX_JAM_TRACK_DOWNLOADS}")
@ -102,8 +105,8 @@ module JamRuby
end end
def delete_s3_files def delete_s3_files
remove_url_48! remove_url_48!
remove_url_44! remove_url_44!
end end
@ -211,6 +214,86 @@ module JamRuby
.where('jam_tracks.id IN (?)', jamtracks) .where('jam_tracks.id IN (?)', jamtracks)
end end
def guard_against_fraud(current_user, fingerprint, remote_ip)
if current_user.blank?
return "no user specified"
end
# admin's get to skip fraud check
if current_user.admin
return nil
end
if fingerprint.nil? || fingerprint.empty?
return "no fingerprint specified"
end
all_fingerprint = fingerprint[:all]
running_fingerprint = fingerprint[:running]
if all_fingerprint.blank?
return "no all fingerprint specified"
end
if running_fingerprint.blank?
return "no running fingerprint specified"
end
if redeemed && !redeemed_and_fingerprinted
# if this is a free JamTrack, we need to check for fraud or accidental misuse
# first of all, does this user have any other JamTracks aside from this one that have already been redeemed it and are marked free?
other_redeemed_freebie = JamTrackRight.where(redeemed: true).where(redeemed_and_fingerprinted: true).where('id != ?', id).where(user_id: current_user.id).first
if other_redeemed_freebie
return "already redeemed another"
end
# can we find a jam track that belongs to someone else with the same fingerprint
match = MachineFingerprint.find_by_fingerprint(all_fingerprint)
if match && match.user != current_user
AdminMailer.alerts(subject: "'All' fingerprint collision by #{current_user.name}",
body: "MachineFingerprint #{match.inspect}\n\nCurrent User: #{current_user.admin_url}").deliver
# try to record the other fingerprint
MachineFingerprint.create(running_fingerprint, current_user, MachineFingerprint::TAKEN_ON_FRAUD_CONFLICT, MachineFingerprint::PRINT_TYPE_ACTIVE, remote_ip, self)
if APP_CONFIG.error_on_fraud
return "other user has 'all' fingerprint"
end
end
if all_fingerprint != running_fingerprint
match = MachineFingerprint.find_by_fingerprint(running_fingerprint)
if match && match.user != current_user
AdminMailer.alerts(subject: "'Running' fingerprint collision by #{current_user.name}",
body: "MachineFingerprint #{match.inspect}\n\nCurrent User: #{current_user.admin_url}").deliver
# try to record the other fingerprint
MachineFingerprint.create(all_fingerprint, current_user, MachineFingerprint::TAKEN_ON_FRAUD_CONFLICT, MachineFingerprint::PRINT_TYPE_ALL, remote_ip, self)
if APP_CONFIG.error_on_fraud
return "other user has 'running' fingerprint"
end
end
end
# we made it past all checks; let's slap on the redeemed_fingerprint
self.redeemed_and_fingerprinted = true
MachineFingerprint.create(all_fingerprint, current_user, MachineFingerprint::TAKEN_ON_SUCCESSFUL_DOWNLOAD, MachineFingerprint::PRINT_TYPE_ALL, remote_ip, self)
if all_fingerprint != running_fingerprint
MachineFingerprint.create(running_fingerprint, current_user, MachineFingerprint::TAKEN_ON_SUCCESSFUL_DOWNLOAD, MachineFingerprint::PRINT_TYPE_ACTIVE, remote_ip, self)
end
save!
end
nil
end
def self.stats def self.stats
stats = {} stats = {}

View File

@ -0,0 +1,35 @@
module JamRuby
class MachineFingerprint < ActiveRecord::Base
@@log = Logging.logger[MachineFingerprint]
belongs_to :user, :class_name => "JamRuby::User"
belongs_to :jam_track_right, :class_name => "JamRuby::JamTrackRight"
TAKEN_ON_SUCCESSFUL_DOWNLOAD = 'dl'
TAKEN_ON_FRAUD_CONFLICT = 'fc'
PRINT_TYPE_ALL = 'a'
PRINT_TYPE_ACTIVE = 'r'
validates :user, presence:true
validates :when_taken, :inclusion => {:in => [TAKEN_ON_SUCCESSFUL_DOWNLOAD, TAKEN_ON_FRAUD_CONFLICT]}
validates :fingerprint, presence: true, uniqueness:true
validates :print_type, presence: true, :inclusion => {:in =>[PRINT_TYPE_ALL, PRINT_TYPE_ACTIVE]}
validates :remote_ip, presence: true
def self.create(fingerprint, user, when_taken, print_type, remote_ip, jam_track_right = nil)
mf = MachineFingerprint.new
mf.fingerprint = fingerprint
mf.user = user
mf.when_taken = when_taken
mf.print_type = print_type
mf.remote_ip = remote_ip
mf.jam_track_right = jam_track_right
unless mf.save
@@log.error("unable to create machine fingerprint: #{mf.errors.inspect}")
end
end
end
end

View File

@ -128,6 +128,10 @@ module JamRuby
# just a pain to implement # just a pain to implement
end end
def self.is_only_freebie(shopping_carts_jam_tracks)
shopping_carts_jam_tracks.length == 1 && shopping_carts_jam_tracks[0].product_info[:free]
end
# this method will either return a valid sale, or throw a RecurlyClientError or ActiveRecord validation error (save! failed) # this method will either return a valid sale, or throw a RecurlyClientError or ActiveRecord validation error (save! failed)
# it may return an nil sale if the JamTrack(s) specified by the shopping carts are already owned # it may return an nil sale if the JamTrack(s) specified by the shopping carts are already owned
def self.order_jam_tracks(current_user, shopping_carts_jam_tracks) def self.order_jam_tracks(current_user, shopping_carts_jam_tracks)
@ -139,58 +143,76 @@ module JamRuby
sale = create_jam_track_sale(current_user) sale = create_jam_track_sale(current_user)
if sale.valid? if sale.valid?
account = client.get_account(current_user) if is_only_freebie(shopping_carts_jam_tracks)
if account.present? sale.process_jam_tracks(current_user, shopping_carts_jam_tracks, nil)
purge_pending_adjustments(account) sale.recurly_subtotal_in_cents = 0
sale.recurly_tax_in_cents = 0
sale.recurly_total_in_cents = 0
sale.recurly_currency = 'USD'
created_adjustments = sale.process_jam_tracks(current_user, shopping_carts_jam_tracks, account) sale_line_item = sale.sale_line_items[0]
sale_line_item.recurly_tax_in_cents = 0
sale_line_item.recurly_total_in_cents = 0
sale_line_item.recurly_currency = 'USD'
sale_line_item.recurly_discount_in_cents = 0
sale.save
# now invoice the sale ... almost done else
begin account = client.get_account(current_user)
invoice = account.invoice! if account.present?
sale.recurly_invoice_id = invoice.uuid
sale.recurly_invoice_number = invoice.invoice_number
# now slap in all the real tax/purchase totals purge_pending_adjustments(account)
sale.recurly_subtotal_in_cents = invoice.subtotal_in_cents
sale.recurly_tax_in_cents = invoice.tax_in_cents created_adjustments = sale.process_jam_tracks(current_user, shopping_carts_jam_tracks, account)
sale.recurly_total_in_cents = invoice.total_in_cents
sale.recurly_currency = invoice.currency # now invoice the sale ... almost done
begin
invoice = account.invoice!
sale.recurly_invoice_id = invoice.uuid
sale.recurly_invoice_number = invoice.invoice_number
# now slap in all the real tax/purchase totals
sale.recurly_subtotal_in_cents = invoice.subtotal_in_cents
sale.recurly_tax_in_cents = invoice.tax_in_cents
sale.recurly_total_in_cents = invoice.total_in_cents
sale.recurly_currency = invoice.currency
# and resolve against sale_line_items
sale.sale_line_items.each do |sale_line_item|
found_line_item = false
invoice.line_items.each do |line_item|
if line_item.uuid == sale_line_item.recurly_adjustment_uuid
sale_line_item.recurly_tax_in_cents = line_item.tax_in_cents
sale_line_item.recurly_total_in_cents =line_item.total_in_cents
sale_line_item.recurly_currency = line_item.currency
sale_line_item.recurly_discount_in_cents = line_item.discount_in_cents
found_line_item = true
break
end
# and resolve against sale_line_items
sale.sale_line_items.each do |sale_line_item|
found_line_item = false
invoice.line_items.each do |line_item|
if line_item.uuid == sale_line_item.recurly_adjustment_uuid
sale_line_item.recurly_tax_in_cents = line_item.tax_in_cents
sale_line_item.recurly_total_in_cents =line_item.total_in_cents
sale_line_item.recurly_currency = line_item.currency
sale_line_item.recurly_discount_in_cents = line_item.discount_in_cents
found_line_item = true
break
end end
if !found_line_item
@@log.error("can't find line item #{sale_line_item.recurly_adjustment_uuid}")
puts "CANT FIND LINE ITEM"
end
end end
if !found_line_item unless sale.save
@@log.error("can't find line item #{sale_line_item.recurly_adjustment_uuid}") raise RecurlyClientError, "Invalid sale (at end)."
puts "CANT FIND LINE ITEM"
end end
rescue Recurly::Resource::Invalid => e
# this exception is thrown by invoice! if the invoice is invalid
sale.rollback_adjustments(current_user, created_adjustments)
sale = nil
raise ActiveRecord::Rollback # kill all db activity, but don't break outside logic
end end
else
unless sale.save raise RecurlyClientError, "Could not find account to place order."
raise RecurlyClientError, "Invalid sale (at end)."
end
rescue Recurly::Resource::Invalid => e
# this exception is thrown by invoice! if the invoice is invalid
sale.rollback_adjustments(current_user, created_adjustments)
sale = nil
raise ActiveRecord::Rollback # kill all db activity, but don't break outside logic
end end
else
raise RecurlyClientError, "Could not find account to place order."
end end
else else
raise RecurlyClientError, "Invalid sale." raise RecurlyClientError, "Invalid sale."
@ -238,30 +260,33 @@ module JamRuby
return return
end end
# ask the shopping cart to create the correct Recurly adjustment attributes for a JamTrack if account
adjustments = shopping_cart.create_adjustment_attributes(current_user) # ask the shopping cart to create the correct Recurly adjustment attributes for a JamTrack
adjustments = shopping_cart.create_adjustment_attributes(current_user)
adjustments.each do |adjustment| adjustments.each do |adjustment|
# create the adjustment at Recurly (this may not look like it, but it is a REST API) # create the adjustment at Recurly (this may not look like it, but it is a REST API)
created_adjustment = account.adjustments.new(adjustment) created_adjustment = account.adjustments.new(adjustment)
created_adjustment.save created_adjustment.save
# if the adjustment could not be made, bail # if the adjustment could not be made, bail
raise RecurlyClientError.new(created_adjustment.errors) if created_adjustment.errors.any? raise RecurlyClientError.new(created_adjustment.errors) if created_adjustment.errors.any?
# keep track of adjustments we created for this order, in case we have to roll them back # keep track of adjustments we created for this order, in case we have to roll them back
created_adjustments << created_adjustment created_adjustments << created_adjustment
if ShoppingCart.is_product_purchase?(adjustment) if ShoppingCart.is_product_purchase?(adjustment)
# this was a normal product adjustment, so track it as such # this was a normal product adjustment, so track it as such
recurly_adjustment_uuid = created_adjustment.uuid recurly_adjustment_uuid = created_adjustment.uuid
else else
# this was a 'credit' adjustment, so track it as such # this was a 'credit' adjustment, so track it as such
recurly_adjustment_credit_uuid = created_adjustment.uuid recurly_adjustment_credit_uuid = created_adjustment.uuid
end
end end
end end
# create one sale line item for every jam track # create one sale line item for every jam track
sale_line_item = SaleLineItem.create_from_shopping_cart(self, shopping_cart, nil, recurly_adjustment_uuid, recurly_adjustment_credit_uuid) sale_line_item = SaleLineItem.create_from_shopping_cart(self, shopping_cart, nil, recurly_adjustment_uuid, recurly_adjustment_credit_uuid)
@ -279,7 +304,11 @@ module JamRuby
end end
# also if the purchase was a free one, then update the user record to no longer allow redeemed jamtracks # also if the purchase was a free one, then update the user record to no longer allow redeemed jamtracks
User.where(id: current_user.id).update_all(has_redeemable_jamtrack: false) if shopping_cart.free? if shopping_cart.free?
User.where(id: current_user.id).update_all(has_redeemable_jamtrack: false)
current_user.has_redeemable_jamtrack = false # make sure model reflects the truth
end
# this can't go in the block above, as it's here to fix bad subscription UUIDs in an update path # 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_adjustment_uuid != recurly_adjustment_uuid if jam_track_right.recurly_adjustment_uuid != recurly_adjustment_uuid

View File

@ -152,6 +152,12 @@ module JamRuby
def self.add_jam_track_to_cart(any_user, jam_track) def self.add_jam_track_to_cart(any_user, jam_track)
cart = nil cart = nil
ShoppingCart.transaction do ShoppingCart.transaction do
if any_user.has_redeemable_jamtrack
# if you still have a freebie available to you, or if you are an anonymous user, we make sure there is nothing else in your shopping cart
any_user.destroy_all_shopping_carts
end
mark_redeem = ShoppingCart.user_has_redeemable_jam_track?(any_user) mark_redeem = ShoppingCart.user_has_redeemable_jam_track?(any_user)
cart = ShoppingCart.create(any_user, jam_track, 1, mark_redeem) cart = ShoppingCart.create(any_user, jam_track, 1, mark_redeem)
end end
@ -173,7 +179,13 @@ module JamRuby
carts[0].save carts[0].save
end end
end end
end
def port(user, anonymous_user)
ShoppingCart.transaction do
move_to_user(user, anonymous_user, anonymous_user.shopping_carts)
end
end end
end end
end end

View File

@ -6,6 +6,7 @@ module JamRuby
# we use it to figure out what to do with the user after they signup # we use it to figure out what to do with the user after they signup
class SignupHint < ActiveRecord::Base class SignupHint < ActiveRecord::Base
belongs_to :jam_track, class_name: 'JamRuby::JamTrack'
belongs_to :user, class_name: 'JamRuby::User' belongs_to :user, class_name: 'JamRuby::User'
@ -23,6 +24,7 @@ module JamRuby
hint.anonymous_user_id = anonymous_user.id hint.anonymous_user_id = anonymous_user.id
hint.redirect_location = options[:redirect_location] if options.has_key?(:redirect_location) hint.redirect_location = options[:redirect_location] if options.has_key?(:redirect_location)
hint.want_jamblaster = options[:want_jamblaster] if options.has_key?(:want_jamblaster) hint.want_jamblaster = options[:want_jamblaster] if options.has_key?(:want_jamblaster)
#hint.jam_track = JamTrack.find(options[:jam_track]) if options.has_key?(:jam_track)
hint.expires_at = 15.minutes.from_now hint.expires_at = 15.minutes.from_now
hint.save hint.save
hint hint

View File

@ -990,6 +990,7 @@ module JamRuby
recaptcha_failed = options[:recaptcha_failed] recaptcha_failed = options[:recaptcha_failed]
any_user = options[:any_user] any_user = options[:any_user]
reuse_card = options[:reuse_card] reuse_card = options[:reuse_card]
signup_hint = options[:signup_hint]
user = User.new user = User.new
@ -1055,8 +1056,6 @@ module JamRuby
end end
end end
unless fb_signup.nil? unless fb_signup.nil?
user.update_fb_authorization(fb_signup) user.update_fb_authorization(fb_signup)
@ -1109,6 +1108,18 @@ module JamRuby
user.save user.save
# if the user has just one, free jamtrack in their shopping cart, and it matches the signup hint, then auto-buy it
# only_freebie_in_cart =
# signup_hint &&
# signup_hint.jam_track &&
# user.shopping_carts.length == 1 &&
# user.shopping_carts[0].cart_product == signup_hint.jam_track &&
# user.shopping_carts[0].product_info[:free]
#
# if only_freebie_in_cart
# Sale.place_order(user, user.shopping_carts)
# end
user.errors.add("recaptcha", "verification failed") if recaptcha_failed user.errors.add("recaptcha", "verification failed") if recaptcha_failed
if user.errors.any? if user.errors.any?
@ -1614,6 +1625,21 @@ module JamRuby
APP_CONFIG.admin_root_url + "/admin/jam_track_rights?q[user_id_equals]=#{id}&commit=Filter&order=created_at DESC" APP_CONFIG.admin_root_url + "/admin/jam_track_rights?q[user_id_equals]=#{id}&commit=Filter&order=created_at DESC"
end end
# these are signup attributes that we default to when not presenting the typical form @ /signup
def self.musician_defaults(remote_ip, confirmation_url, any_user, options)
options = options || {}
options[:remote_ip] = remote_ip
options[:birth_date] = nil
options[:instruments] = [{:instrument_id => 'other', :proficiency_level => 1, :priority => 1}]
options[:musician] = true
options[:skip_recaptcha] = true
options[:invited_user] = nil
options[:fb_signup] = nil
options[:signup_confirm_url] = confirmation_url
options[:any_user] = any_user
options
end
private private
def create_remember_token def create_remember_token
self.remember_token = SecureRandom.urlsafe_base64 self.remember_token = SecureRandom.urlsafe_base64

View File

@ -208,5 +208,125 @@ describe JamTrackRight do
end end
end end
describe "guard_against_fraud" do
let(:user) {FactoryGirl.create(:user)}
let(:other) {FactoryGirl.create(:user)}
let(:first_fingerprint) { {all: 'all', running: 'running' } }
let(:new_fingerprint) { {all: 'all_2', running: 'running' } }
let(:remote_ip) {'1.1.1.1'}
let(:jam_track_right) { FactoryGirl.create(:jam_track_right, user: user, redeemed: true, redeemed_and_fingerprinted: false) }
let(:jam_track_right_purchased) { FactoryGirl.create(:jam_track_right, user: user, redeemed: false, redeemed_and_fingerprinted: false) }
let(:jam_track_right_other) { FactoryGirl.create(:jam_track_right, user: other, redeemed: true, redeemed_and_fingerprinted: false) }
let(:jam_track_right_other_purchased) { FactoryGirl.create(:jam_track_right, user: other, redeemed: false, redeemed_and_fingerprinted: false) }
it "denies no current_user" do
jam_track_right.guard_against_fraud(nil, first_fingerprint, remote_ip).should eq('no user specified')
end
it "denies no fingerprint" do
jam_track_right.guard_against_fraud(user, nil, remote_ip).should eq('no fingerprint specified')
end
it "allows redemption (success)" do
jam_track_right.guard_against_fraud(user, first_fingerprint, remote_ip).should be_nil
jam_track_right.valid?.should be_true
jam_track_right.redeemed_and_fingerprinted.should be_true
mf = MachineFingerprint.find_by_fingerprint(first_fingerprint[:all])
mf.user.should eq(user)
mf.fingerprint.should eq(first_fingerprint[:all])
mf.when_taken.should eq(MachineFingerprint::TAKEN_ON_SUCCESSFUL_DOWNLOAD)
mf.print_type.should eq(MachineFingerprint::PRINT_TYPE_ALL)
mf.jam_track_right.should eq(jam_track_right)
mf = MachineFingerprint.find_by_fingerprint(first_fingerprint[:running])
mf.user.should eq(user)
mf.fingerprint.should eq(first_fingerprint[:running])
mf.when_taken.should eq(MachineFingerprint::TAKEN_ON_SUCCESSFUL_DOWNLOAD)
mf.print_type.should eq(MachineFingerprint::PRINT_TYPE_ACTIVE)
mf.jam_track_right.should eq(jam_track_right)
end
it "ignores already successfully redeemed" do
jam_track_right.redeemed_and_fingerprinted = true
jam_track_right.save!
jam_track_right.guard_against_fraud(user, new_fingerprint, remote_ip).should be_nil
jam_track_right.valid?.should be_true
# and no new fingerprints
MachineFingerprint.count.should eq(0)
end
it "ignores already normally purchased" do
jam_track_right.guard_against_fraud(user, first_fingerprint, remote_ip)
MachineFingerprint.count.should eq(2)
jam_track_right_purchased.guard_against_fraud(user, new_fingerprint, remote_ip).should be_nil
jam_track_right_purchased.valid?.should be_true
jam_track_right_purchased.redeemed_and_fingerprinted.should be_false # fingerprint should not be set on normal purchase
jam_track_right.redeemed_and_fingerprinted.should be_true # should still be redeemed_and fingerprinted; just checking for weird side-effects
# no new fingerprints
MachineFingerprint.count.should eq(2)
end
it "protects against re-using fingerprint across users (conflicts on all fp)" do
jam_track_right.guard_against_fraud(user, first_fingerprint, remote_ip).should be_nil
MachineFingerprint.count.should eq(2)
first_fingerprint[:running] = 'running_2'
jam_track_right_other.guard_against_fraud(other, first_fingerprint, remote_ip).should eq("other user has 'all' fingerprint")
mf = MachineFingerprint.find_by_fingerprint(first_fingerprint[:running])
mf.user.should eq(other)
mf.fingerprint.should eq(first_fingerprint[:running])
mf.when_taken.should eq(MachineFingerprint::TAKEN_ON_FRAUD_CONFLICT)
mf.print_type.should eq(MachineFingerprint::PRINT_TYPE_ACTIVE)
mf.jam_track_right.should eq(jam_track_right_other)
MachineFingerprint.count.should eq(3)
end
it "protects against re-using fingerprint across users (conflicts on running fp)" do
jam_track_right.guard_against_fraud(user, first_fingerprint, remote_ip).should be_nil
MachineFingerprint.count.should eq(2)
first_fingerprint[:all] = 'all_2'
jam_track_right_other.guard_against_fraud(other, first_fingerprint, remote_ip).should eq("other user has 'running' fingerprint")
mf = MachineFingerprint.find_by_fingerprint(first_fingerprint[:all])
mf.user.should eq(other)
mf.fingerprint.should eq(first_fingerprint[:all])
mf.when_taken.should eq(MachineFingerprint::TAKEN_ON_FRAUD_CONFLICT)
mf.print_type.should eq(MachineFingerprint::PRINT_TYPE_ALL)
mf.jam_track_right.should eq(jam_track_right_other)
MachineFingerprint.count.should eq(3)
end
# if you try to buy a regular jamtrack with a fingerprint belonging to another user? so what. you paid for it
it "allows re-use of fingerprint if jamtrack is a normal purchase" do
jam_track_right.guard_against_fraud(user, first_fingerprint, remote_ip).should be_nil
MachineFingerprint.count.should eq(2)
jam_track_right_other_purchased.guard_against_fraud(other, first_fingerprint, remote_ip).should be_nil
MachineFingerprint.count.should eq(2)
end
it "stops you from redeeming two jamtracks" do
right1 = FactoryGirl.create(:jam_track_right, user: user, redeemed: true, redeemed_and_fingerprinted: true)
jam_track_right.guard_against_fraud(user, first_fingerprint, remote_ip).should eq('already redeemed another')
MachineFingerprint.count.should eq(0)
end
it "let's you download a free jamtrack if you have a second but undownloaded free one" do
right1 = FactoryGirl.create(:jam_track_right, user: user, redeemed: true, redeemed_and_fingerprinted: false)
jam_track_right.guard_against_fraud(user, first_fingerprint, remote_ip).should be_nil
MachineFingerprint.count.should eq(2)
right1.guard_against_fraud(user, first_fingerprint, remote_ip).should eq('already redeemed another')
MachineFingerprint.count.should eq(2)
end
end
end end

View File

@ -87,9 +87,9 @@ describe Sale do
sales.should eq(user.sales) sales.should eq(user.sales)
sale = sales[0] sale = sales[0]
sale.recurly_invoice_id.should_not be_nil sale.recurly_invoice_id.should be_nil
sale.recurly_subtotal_in_cents.should eq(jam_track_price_in_cents) sale.recurly_subtotal_in_cents.should eq(0)
sale.recurly_tax_in_cents.should eq(0) sale.recurly_tax_in_cents.should eq(0)
sale.recurly_total_in_cents.should eq(0) sale.recurly_total_in_cents.should eq(0)
sale.recurly_currency.should eq('USD') sale.recurly_currency.should eq('USD')
@ -97,7 +97,7 @@ describe Sale do
sale.sale_line_items.length.should == 1 sale.sale_line_items.length.should == 1
sale_line_item = sale.sale_line_items[0] sale_line_item = sale.sale_line_items[0]
sale_line_item.recurly_tax_in_cents.should eq(0) sale_line_item.recurly_tax_in_cents.should eq(0)
sale_line_item.recurly_total_in_cents.should eq(jam_track_price_in_cents) sale_line_item.recurly_total_in_cents.should eq(0)
sale_line_item.recurly_currency.should eq('USD') sale_line_item.recurly_currency.should eq('USD')
sale_line_item.recurly_discount_in_cents.should eq(0) sale_line_item.recurly_discount_in_cents.should eq(0)
sale_line_item.product_type.should eq(JamTrack::PRODUCT_TYPE) sale_line_item.product_type.should eq(JamTrack::PRODUCT_TYPE)
@ -109,8 +109,8 @@ describe Sale do
sale_line_item.recurly_plan_code.should eq(jamtrack.plan_code) sale_line_item.recurly_plan_code.should eq(jamtrack.plan_code)
sale_line_item.product_id.should eq(jamtrack.id) sale_line_item.product_id.should eq(jamtrack.id)
sale_line_item.recurly_subscription_uuid.should be_nil sale_line_item.recurly_subscription_uuid.should be_nil
sale_line_item.recurly_adjustment_uuid.should_not be_nil sale_line_item.recurly_adjustment_uuid.should be_nil
sale_line_item.recurly_adjustment_credit_uuid.should_not be_nil sale_line_item.recurly_adjustment_credit_uuid.should be_nil
sale_line_item.recurly_adjustment_uuid.should eq(user.jam_track_rights.last.recurly_adjustment_uuid) sale_line_item.recurly_adjustment_uuid.should eq(user.jam_track_rights.last.recurly_adjustment_uuid)
sale_line_item.recurly_adjustment_credit_uuid.should eq(user.jam_track_rights.last.recurly_adjustment_credit_uuid) sale_line_item.recurly_adjustment_credit_uuid.should eq(user.jam_track_rights.last.recurly_adjustment_credit_uuid)
@ -118,31 +118,11 @@ describe Sale do
recurly_account = client.get_account(user) recurly_account = client.get_account(user)
adjustments = recurly_account.adjustments adjustments = recurly_account.adjustments
adjustments.should_not be_nil adjustments.should_not be_nil
adjustments.should have(2).items adjustments.should have(0).items
free_purchase= adjustments[0]
free_purchase.unit_amount_in_cents.should eq((jamtrack.price * 100).to_i)
free_purchase.accounting_code.should eq(ShoppingCart::PURCHASE_FREE)
free_purchase.description.should eq("JamTrack: " + jamtrack.name)
free_purchase.state.should eq('invoiced')
free_purchase.uuid.should eq(sale_line_item.recurly_adjustment_uuid)
free_credit = adjustments[1]
free_credit.unit_amount_in_cents.should eq(-(jamtrack.price * 100).to_i)
free_credit.accounting_code.should eq(ShoppingCart::PURCHASE_FREE_CREDIT)
free_credit.description.should eq("JamTrack: " + jamtrack.name + " (Credit)")
free_credit.state.should eq('invoiced')
free_credit.uuid.should eq(sale_line_item.recurly_adjustment_credit_uuid)
invoices = recurly_account.invoices invoices = recurly_account.invoices
invoices.should have(1).items invoices.should have(0).items
invoice = invoices[0]
invoice.uuid.should eq(sale.recurly_invoice_id)
invoice.line_items.should have(2).items # should have both adjustments associated
invoice.line_items[0].should eq(free_credit)
invoice.line_items[1].should eq(free_purchase)
invoice.subtotal_in_cents.should eq((jamtrack.price * 100).to_i)
invoice.total_in_cents.should eq(0)
invoice.state.should eq('collected')
# verify jam_track_rights data # verify jam_track_rights data
user.jam_track_rights.should_not be_nil user.jam_track_rights.should_not be_nil
@ -238,7 +218,7 @@ describe Sale do
# also, verify that no earlier adjustments were affected # also, verify that no earlier adjustments were affected
recurly_account = client.get_account(user) recurly_account = client.get_account(user)
adjustments = recurly_account.adjustments adjustments = recurly_account.adjustments
adjustments.should have(2).items adjustments.should have(0).items # because the only successful purchase was a freebie, there should be no recurly adjustments
end end
# this test counts on the fact that two adjustments are made when buying a free JamTrack # this test counts on the fact that two adjustments are made when buying a free JamTrack
@ -246,13 +226,13 @@ describe Sale do
# we can see if the first one is ultimately destroyed # we can see if the first one is ultimately destroyed
it "rolls back created adjustments if error" do it "rolls back created adjustments if error" do
shopping_cart = ShoppingCart.create user, jamtrack, 1, true shopping_cart = ShoppingCart.create user, jamtrack, 1, false
# grab the real response; we will modify it to make a nil accounting code # grab the real response; we will modify it to make a nil accounting code
adjustment_attrs = shopping_cart.create_adjustment_attributes(user) adjustment_attrs = shopping_cart.create_adjustment_attributes(user)
client.find_or_create_account(user, billing_info) client.find_or_create_account(user, billing_info)
adjustment_attrs[1][:unit_amount_in_cents] = nil # invalid amount adjustment_attrs[0][:unit_amount_in_cents] = nil # invalid amount
ShoppingCart.any_instance.stub(:create_adjustment_attributes).and_return(adjustment_attrs) ShoppingCart.any_instance.stub(:create_adjustment_attributes).and_return(adjustment_attrs)
expect { Sale.place_order(user, [shopping_cart]) }.to raise_error(JamRuby::RecurlyClientError) expect { Sale.place_order(user, [shopping_cart]) }.to raise_error(JamRuby::RecurlyClientError)
@ -265,7 +245,7 @@ describe Sale do
end end
it "rolls back adjustments created before the order" do it "rolls back adjustments created before the order" do
shopping_cart = ShoppingCart.create user, jamtrack, 1, true shopping_cart = ShoppingCart.create user, jamtrack, 1, false
client.find_or_create_account(user, billing_info) client.find_or_create_account(user, billing_info)
# create a single adjustment on the account # create a single adjustment on the account
@ -281,7 +261,7 @@ describe Sale do
recurly_account = client.get_account(user) recurly_account = client.get_account(user)
adjustments = recurly_account.adjustments adjustments = recurly_account.adjustments
adjustments.should have(2).items # two adjustments are created for a free jamtrack; that should be all there is adjustments.should have(1).items # two adjustments are created for a free jamtrack; that should be all there is
end end
end end

View File

@ -21,34 +21,36 @@ describe ShoppingCart do
user.shopping_carts[0].quantity.should == 1 user.shopping_carts[0].quantity.should == 1
end end
it "maintains only one fre JamTrack in ShoppingCart" do
cart1 = ShoppingCart.add_jam_track_to_cart(user, jam_track)
cart1.should_not be_nil
cart1.errors.any?.should be_false
user.reload
cart2 = ShoppingCart.add_jam_track_to_cart(user, jam_track)
cart2.errors.any?.should be_false
user.reload
user.shopping_carts.length.should eq(1)
cart3 = ShoppingCart.add_jam_track_to_cart(user, jam_track2)
cart3.errors.any?.should be_false
user.reload
user.shopping_carts.length.should eq(1)
end
it "should not add duplicate JamTrack to ShoppingCart" do it "should not add duplicate JamTrack to ShoppingCart" do
user.has_redeemable_jamtrack = false
user.save!
cart1 = ShoppingCart.add_jam_track_to_cart(user, jam_track) cart1 = ShoppingCart.add_jam_track_to_cart(user, jam_track)
cart1.should_not be_nil cart1.should_not be_nil
cart1.errors.any?.should be_false cart1.errors.any?.should be_false
user.reload user.reload
cart2 = ShoppingCart.add_jam_track_to_cart(user, jam_track) cart2 = ShoppingCart.add_jam_track_to_cart(user, jam_track)
cart2.errors.any?.should be_true cart2.errors.any?.should be_true
end end
describe "redeemable behavior" do describe "redeemable behavior" do
it "adds redeemable item to shopping cart" do
user.has_redeemable_jamtrack.should be_true it "removes redeemable item to shopping cart (maintains only one in cart)" do
# 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 user.has_redeemable_jamtrack.should be_true
cart1 = ShoppingCart.add_jam_track_to_cart(user, jam_track) cart1 = ShoppingCart.add_jam_track_to_cart(user, jam_track)
@ -58,12 +60,12 @@ describe ShoppingCart do
cart2.should_not be_nil cart2.should_not be_nil
cart1.marked_for_redeem.should eq(1) cart1.marked_for_redeem.should eq(1)
cart2.marked_for_redeem.should eq(0) cart2.marked_for_redeem.should eq(1)
ShoppingCart.remove_jam_track_from_cart(user, jam_track) ShoppingCart.remove_jam_track_from_cart(user, jam_track)
user.shopping_carts.length.should eq(1) user.shopping_carts.length.should eq(0)
cart2.reload cart2.reload
cart1.marked_for_redeem.should eq(1) cart2.marked_for_redeem.should eq(1)
end end
end end
end end

View File

@ -2,7 +2,7 @@ require 'spec_helper'
describe SignupHint do describe SignupHint do
let(:user) {AnonymousUser.new(SecureRandom.uuid)} let(:user) {AnonymousUser.new(SecureRandom.uuid, nil)}
describe "refresh_by_anoymous_user" do describe "refresh_by_anoymous_user" do
it "creates" do it "creates" do

View File

@ -194,6 +194,10 @@ def app_config
'blah' 'blah'
end end
def error_on_fraud
true
end
private private
def audiomixer_workspace_path def audiomixer_workspace_path

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@ -48,6 +48,10 @@
//= require utils //= require utils
//= require subscription_utils //= require subscription_utils
//= require custom_controls //= require custom_controls
//= require web/signup_helper
//= require web/signin_helper
//= require web/signin
//= require web/tracking
//= require_directory . //= require_directory .
//= require_directory ./dialog //= require_directory ./dialog
//= require_directory ./wizard //= require_directory ./wizard

View File

@ -560,20 +560,6 @@
$screen.find("#payment-info-next").on('click', next); $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) { function toggleShippingAsBilling(e) {
e.preventDefault(); e.preventDefault();

View File

@ -19,6 +19,7 @@
var $contentHolder = null; var $contentHolder = null;
var $btnNext = null; var $btnNext = null;
var $btnFacebook = null; var $btnFacebook = null;
var checkoutUtils = context.JK.CheckoutUtilsInstance;
function beforeShow(data) { function beforeShow(data) {
renderNavigation(); renderNavigation();
@ -96,9 +97,23 @@
$signinBtn.text('TRYING...').addClass('disabled') $signinBtn.text('TRYING...').addClass('disabled')
rest.login({email: email, password: password, remember_me: true}) rest.login({email: email, password: password, remember_me: true})
.done(function() { .done(function(user) {
window.location = '/client#/checkoutPayment' // now determine where we should send the user
window.location.reload(); rest.getShoppingCarts()
.done(function(carts) {
if(checkoutUtils.hasOneFreeItemInShoppingCart(carts)) {
window.location = '/client#/redeemComplete'
window.location.reload();
}
else {
window.location = '/client#/checkoutPayment'
window.location.reload();
}
})
.fail(function() {
window.location = '/client#/jamtrackBrowse'
window.location.reload();
})
}) })
.fail(function(jqXHR) { .fail(function(jqXHR) {
if(jqXHR.status == 422) { if(jqXHR.status == 422) {

View File

@ -43,6 +43,16 @@ class CheckoutUtils
getLastPurchase: () => getLastPurchase: () =>
return @lastPurchaseResponse return @lastPurchaseResponse
hasOneFreeItemInShoppingCart: (carts) =>
if carts.length == 0
# nothing is in the user's shopping cart. They shouldn't be here.
return false;
else if carts.length > 1
# the user has multiple items in their shopping cart. They shouldn't be here.
return false;
return carts[0].product_info.free
# global instance # global instance
context.JK.CheckoutUtilsInstance = new CheckoutUtils() context.JK.CheckoutUtilsInstance = new CheckoutUtils()

View File

@ -195,7 +195,7 @@
var obj = { var obj = {
method: 'feed', method: 'feed',
link: signupUrl, link: signupUrl,
picture: 'http://www.jamkazam.com/assets/web/logo-256.png', picture: 'https://www.jamkazam.com/assets/web/logo-256.png',
name: 'Join me on JamKazam', name: 'Join me on JamKazam',
caption: 'Play live music in real-time sessions with others over the Internet, as if in the same room.', caption: 'Play live music in real-time sessions with others over the Internet, as if in the same room.',
description: '', description: '',

View File

@ -89,6 +89,7 @@
rest.openJamTrack({id: context.JK.CurrentSessionModel.id(), jam_track_id: jamTrack.id}) rest.openJamTrack({id: context.JK.CurrentSessionModel.id(), jam_track_id: jamTrack.id})
.done(function(response) { .done(function(response) {
$dialog.data('result', {success:true, jamTrack: jamTrack}) $dialog.data('result', {success:true, jamTrack: jamTrack})
context.JK.CurrentSessionModel.updateSession(response);
app.layout.closeDialog('open-jam-track-dialog'); app.layout.closeDialog('open-jam-track-dialog');
}) })
.fail(function(jqXHR) { .fail(function(jqXHR) {

View File

@ -11,11 +11,21 @@
var dialogId = '#signin-dialog'; var dialogId = '#signin-dialog';
var $dialog = null; var $dialog = null;
var signinHelper = null; var signinHelper = null;
var redirectTo = null;
function beforeShow() { function beforeShow(args) {
logger.debug("showing login form") logger.debug("showing login form")
signinHelper.reset(); if(args.redirect_to) {
redirectTo = "/client#/redeemComplete"
}
else {
redirectTo = null;
}
if(redirectTo) {
logger.debug("setting redirect to in login dialog")
}
signinHelper.reset(redirectTo);
} }
function afterShow() { function afterShow() {
@ -24,6 +34,7 @@
function afterHide() { function afterHide() {
logger.debug("hiding login form") logger.debug("hiding login form")
redirectTo = null;
} }
function initialize(){ function initialize(){

View File

@ -206,6 +206,8 @@ context.JK.DownloadJamTrack = class DownloadJamTrack
showInitial: () => showInitial: () =>
@logger.debug("showing #{@state.name}") @logger.debug("showing #{@state.name}")
@sampleRate = context.jamClient.GetSampleRate() @sampleRate = context.jamClient.GetSampleRate()
@fingerprint = context.jamClient.SessionGetMacHash()
logger.debug("fingerprint: ", @fingerprint)
@sampleRateForFilename = if @sampleRate == 48 then '48' else '44' @sampleRateForFilename = if @sampleRate == 48 then '48' else '44'
@attempts = @attempts + 1 @attempts = @attempts + 1
this.expectTransition() this.expectTransition()
@ -450,7 +452,7 @@ context.JK.DownloadJamTrack = class DownloadJamTrack
@attemptedEnqueue = true @attemptedEnqueue = true
@ajaxEnqueueAborted = false @ajaxEnqueueAborted = false
@rest.enqueueJamTrack({id: @jamTrack.id, sample_rate: @sampleRate}) @rest.enqueueJamTrack({id: @jamTrack.id, sample_rate: @sampleRate, fingerprint: @fingerprint})
.done(this.processEnqueueJamTrack) .done(this.processEnqueueJamTrack)
.fail(this.processEnqueueJamTrackFail) .fail(this.processEnqueueJamTrackFail)
@ -474,9 +476,24 @@ context.JK.DownloadJamTrack = class DownloadJamTrack
else else
@logger.debug("DownloadJamTrack: ignoring processEnqueueJamTrack response") @logger.debug("DownloadJamTrack: ignoring processEnqueueJamTrack response")
processEnqueueJamTrackFail: () => displayUIForGuard:(response) =>
display = switch response.message
when 'no user specified' then 'Please log back in.'
when 'no fingerprint specified' then 'There was a problem communicating between client and server. Please restart JamKazam.'
when 'no all fingerprint specified' then 'There was a problem communicating between client and server. Please restart JamKazam.'
when 'no running fingerprint specified' then 'There was a problem communicating between client and server. Please restart JamKazam.'
when 'already redeemed another' then "It appears you have already redeemed your one free JamTrack for your household. We are sorry, but we cannot let you download this JamTrack free. If you believe this is an error, please contact us at support@jamkazam.com."
when "other user has 'all' fingerprint" then "It appears you have already redeemed your one free JamTrack for your household. We are sorry, but we cannot let you download this JamTrack free. If you believe this is an error, please contact us at support@jamkazam.com."
when "other user has 'running' fingerprint" then "It appears you have already redeemed your one free JamTrack for your household. We are sorry, but we cannot let you download this JamTrack free. If you believe this is an error, please contact us at support@jamkazam.com."
else "Something went wrong #{response.message}. Please restart JamKazam"
processEnqueueJamTrackFail: (jqXHR) =>
unless @ajaxEnqueueAborted unless @ajaxEnqueueAborted
this.transitionError("enqueue-error", "Unable to ask the server to build your JamTrack.") if jqXHR.status == 403
display = this.displayUIForGuard(JSON.parse(jqXHR.responseText))
this.transitionError("enqueue-error", display)
else
this.transitionError("enqueue-error", "Unable to ask the server to build your JamTrack.")
else else
@logger.debug("DownloadJamTrack: ignoring processEnqueueJamTrackFail response") @logger.debug("DownloadJamTrack: ignoring processEnqueueJamTrackFail response")

View File

@ -210,6 +210,7 @@
if (!userProfile.show_jamtrack_guide && userProfile.show_whats_next && userProfile.show_whats_next_count < 10 && if (!userProfile.show_jamtrack_guide && userProfile.show_whats_next && userProfile.show_whats_next_count < 10 &&
window.location.pathname.indexOf(gon.client_path) == 0 && window.location.pathname.indexOf(gon.client_path) == 0 &&
window.location.hash.indexOf('/checkout') == -1 && window.location.hash.indexOf('/checkout') == -1 &&
window.location.hash.indexOf('/redeem') == -1 &&
!app.layout.isDialogShowing('getting-started')) !app.layout.isDialogShowing('getting-started'))
{ {
app.layout.showDialog('getting-started'); app.layout.showDialog('getting-started');
@ -229,8 +230,8 @@
try { try {
cookie = JSON.parse(cookie) cookie = JSON.parse(cookie)
context.JK.signup = {} context.JK.signupData = {}
context.JK.signup = cookie context.JK.signupData = cookie
$(function() { $(function() {
// ga() object isn't ready until the page is loaded // ga() object isn't ready until the page is loaded

View File

@ -1689,6 +1689,26 @@
}); });
} }
function signup(data) {
return $.ajax({
type: "POST",
url: '/api/users',
dataType: "json",
contentType: 'application/json',
data: JSON.stringify(data),
});
}
function portOverCarts() {
return $.ajax({
type: "POST",
url: '/api/shopping_carts/port',
dataType: "json",
contentType: 'application/json',
data: JSON.stringify(data)
})
}
function createAlert(subject, data) { function createAlert(subject, data) {
var message = {subject:subject}; var message = {subject:subject};
$.extend(message, data); $.extend(message, data);
@ -1855,6 +1875,8 @@
this.playJamTrack = playJamTrack; this.playJamTrack = playJamTrack;
this.createSignupHint = createSignupHint; this.createSignupHint = createSignupHint;
this.createAlert = createAlert; this.createAlert = createAlert;
this.signup = signup;
this.portOverCarts = portOverCarts;
return this; return this;
}; };

View File

@ -9,7 +9,7 @@ context.JK.JamTrackPreview = class JamTrackPreview
@EVENTS = context.JK.EVENTS @EVENTS = context.JK.EVENTS
@rest = context.JK.Rest() @rest = context.JK.Rest()
@logger = context.JK.logger @logger = context.JK.logger
@options = options || {master_shows_duration: false, color:'gray'} @options = options || {master_shows_duration: false, color:'gray', add_line_break: false}
@app = app @app = app
@jamTrack = jamTrack @jamTrack = jamTrack
@jamTrackTrack = jamTrackTrack @jamTrackTrack = jamTrackTrack
@ -56,17 +56,26 @@ context.JK.JamTrackPreview = class JamTrackPreview
if @jamTrackTrack.track_type == 'Track' if @jamTrackTrack.track_type == 'Track'
part = "#{@jamTrackTrack.part}" if @jamTrackTrack.part? && @jamTrackTrack.part != instrumentDescription part = "#{@jamTrackTrack.part}" if @jamTrackTrack.part? && @jamTrackTrack.part != instrumentDescription
@part.text("(#{part})") if part != ''
else else
if @options.master_shows_duration if @options.master_adds_line_break
duration = 'entire song' part = '"' + @jamTrack.name + '"' + ' by ' + @jamTrack.original_artist
if @jamTrack.duration
duration = "0:00 - #{context.JK.prettyPrintSeconds(@jamTrack.duration)}" @part.html("#{part}") if part != ''
part = duration @part.addClass('adds-line-break')
else else
part = @jamTrack.name + ' by ' + @jamTrack.original_artist
if @options.master_shows_duration
duration = 'entire song'
if @jamTrack.duration
duration = "#{context.JK.prettyPrintSeconds(@jamTrack.duration)}"
part = duration
else
part = @jamTrack.name + ' by ' + @jamTrack.original_artist
@part.text("(#{part})") if part != ''
@part.text("(#{part})") if part != ''
if @jamTrackTrack.preview_mp3_url? if @jamTrackTrack.preview_mp3_url?

View File

@ -40,6 +40,7 @@ context.JK.JamTrackScreen=class JamTrackScreen
this.refresh() this.refresh()
afterShow:(data) => afterShow:(data) =>
context.JK.Tracking.jamtrackBrowseTrack(@app)
beforeHide: () => beforeHide: () =>
this.clearResults(); this.clearResults();
@ -192,9 +193,23 @@ context.JK.JamTrackScreen=class JamTrackScreen
addToCartJamtrack:(e) => addToCartJamtrack:(e) =>
e.preventDefault() e.preventDefault()
params = id: $(e.target).attr('data-jamtrack-id') $target = $(e.target)
params = id: $target.attr('data-jamtrack-id')
isFree = $(e.target).is('.is_free')
rest.addJamtrackToShoppingCart(params).done((response) => rest.addJamtrackToShoppingCart(params).done((response) =>
context.location = '/client#/shoppingCart' if(isFree)
if context.JK.currentUserId?
alert("TODO")
context.JK.currentUserFreeJamTrack = true # make sure the user sees no more free notices
context.location = '/client#/redeemComplete'
else
# now make a rest call to buy it
context.location = '/client#/redeemSignup'
else
context.location = '/client#/shoppingCart'
).fail @app.ajaxError ).fail @app.ajaxError
licenseUSWhy:(e) => licenseUSWhy:(e) =>
@ -211,15 +226,15 @@ context.JK.JamTrackScreen=class JamTrackScreen
if expand if expand
trackElement.find('.extra').removeClass('hidden') trackElement.find('.extra').removeClass('hidden')
detailArrow.html('hide tracks <a class="details-arrow arrow-up-orange"></a>') detailArrow.html('hide tracks <a class="details-arrow arrow-up"></a>')
for track in jamTrack.tracks for track in jamTrack.tracks
trackElement.find("[jamtrack-track-id='#{track.id}']").removeClass('hidden') trackElement.find("[jamtrack-track-id='#{track.id}']").removeClass('hidden')
else else
trackElement.find('.extra').addClass('hidden') trackElement.find('.extra').addClass('hidden')
detailArrow.html('preview all tracks <a class="details-arrow arrow-down-orange"></a>') detailArrow.html('show all tracks <a class="details-arrow arrow-down"></a>')
count = 0 count = 0
for track in jamTrack.tracks for track in jamTrack.tracks
if count < 2 if count < 6
trackElement.find("[jamtrack-track-id='#{track.id}']").removeClass('hidden') trackElement.find("[jamtrack-track-id='#{track.id}']").removeClass('hidden')
else else
trackElement.find("[jamtrack-track-id='#{track.id}']").addClass('hidden') trackElement.find("[jamtrack-track-id='#{track.id}']").addClass('hidden')
@ -281,14 +296,15 @@ context.JK.JamTrackScreen=class JamTrackScreen
if track.part != '' if track.part != ''
track.instrument_desc += ' (' + track.part + ')' track.instrument_desc += ' (' + track.part + ')'
free_state = if gon.global.one_free_jamtrack_per_user then 'free' else 'non-free' free_state = if context.JK.currentUserFreeJamTrack then 'free' else 'non-free'
if @user
free_state = if @user.free_jamtrack then 'free' else 'non-free' is_free = free_state == 'free'
options = options =
jamtrack: trackRow jamtrack: trackRow
expanded: false expanded: false
free_state: free_state free_state: free_state,
is_free: is_free
@jamtrackItem = $(context._.template($('#template-jamtrack').html(), options, variable: 'data')) @jamtrackItem = $(context._.template($('#template-jamtrack').html(), options, variable: 'data'))
that.renderJamtrack(@jamtrackItem, jamtrack) that.renderJamtrack(@jamtrackItem, jamtrack)
that.registerEvents(@jamtrackItem) that.registerEvents(@jamtrackItem)

View File

@ -0,0 +1,263 @@
(function (context, $) {
"use strict";
context.JK = context.JK || {};
context.JK.RedeemCompleteScreen = function (app) {
var EVENTS = context.JK.EVENTS;
var logger = context.JK.logger;
var rest = context.JK.Rest();
var jamTrackUtils = context.JK.JamTrackUtils;
var checkoutUtils = context.JK.CheckoutUtilsInstance;
var $screen = null;
var $navigation = null;
var $templatePurchasedJamTrack = null;
var $thanksPanel = null;
var $jamTrackInBrowser = null;
var $jamTrackInClient = null;
var $purchasedJamTrack = null;
var $purchasedJamTrackHeader = null;
var $purchasedJamTracks = null;
var userDetail = null;
var step = null;
var downloadJamTracks = [];
var purchasedJamTracks = null;
var purchasedJamTrackIterator = 0;
var $backBtn = null;
var $downloadApplicationLink = null;
var $noPurchasesPrompt = null;
var shoppingCartItem = null;
function beforeShow() {
}
function afterShow(data) {
$noPurchasesPrompt.addClass('hidden')
$purchasedJamTracks.empty()
$thanksPanel.addClass("hidden")
$purchasedJamTrackHeader.attr('status', 'in-progress')
$jamTrackInBrowser.addClass('hidden')
$jamTrackInClient.addClass('hidden')
// if there is no current user, but it apperas we have a login cookie, just refresh
if(!context.JK.currentUserId && $.cookie('remember_token')) {
window.location.reload();
}
else {
redeemJamTrack()
}
//prepThanks()
}
function handleShoppingCartResponse(carts) {
if(!checkoutUtils.hasOneFreeItemInShoppingCart(carts)) {
// the user has multiple items in their shopping cart. They shouldn't be here.
logger.error("invalid access of redeemComplete page")
window.location = '/client#/jamtrackBrowse'
}
else {
// ok, we have one, free item. save it for
shoppingCartItem = carts[0];
rest.placeOrder()
.done(function(purchaseResponse) {
context.JK.currentUserFreeJamTrack = false // make sure the user sees no more free notices without having to do a full page refresh
checkoutUtils.setLastPurchase(purchaseResponse)
jamTrackUtils.checkShoppingCart()
//app.refreshUser() // this only causes grief in tests for some reason, and with currentUserFreeJamTrack = false above, this is probably now unnecessary
prepThanks();
})
.fail(function() {
})
}
}
function redeemJamTrack() {
rest.getShoppingCarts()
.done(handleShoppingCartResponse)
.fail(app.ajaxError);
}
function beforeHide() {
if(downloadJamTracks) {
context._.each(downloadJamTracks, function(downloadJamTrack) {
downloadJamTrack.destroy();
downloadJamTrack.root.remove();
})
downloadJamTracks = [];
}
purchasedJamTracks = null;
purchasedJamTrackIterator = 0;
}
function prepThanks() {
showThanks();
}
function showThanks(purchaseResponse) {
var purchaseResponse = checkoutUtils.getLastPurchase();
if(!purchaseResponse || purchaseResponse.length == 0) {
// user got to this page with no context
logger.debug("no purchases found; nothing to show")
$noPurchasesPrompt.removeClass('hidden')
}
else {
if(gon.isNativeClient) {
$jamTrackInClient.removeClass('hidden')
}
else {
$jamTrackInBrowser.removeClass('hidden');
}
$thanksPanel.removeClass('hidden')
handleJamTracksPurchased(purchaseResponse.jam_tracks)
}
}
function handleJamTracksPurchased(jamTracks) {
// were any JamTracks purchased?
var jamTracksPurchased = jamTracks && jamTracks.length > 0;
if(jamTracksPurchased) {
if(gon.isNativeClient) {
$jamTrackInClient.removeClass('hidden')
context.JK.GA.virtualPageView('/redeemInClient');
startDownloadJamTracks(jamTracks)
}
else {
$jamTrackInBrowser.removeClass('hidden');
app.user().done(function(user) {
// relative to 1 day ago (24 * 60 * 60 * 1000)
if(new Date(user.created_at).getTime() < new Date().getTime() - 86400000) {
logger.debug("existing user recorded")
context.JK.GA.virtualPageView('/redeemInBrowserExistingUser');
}
else {
logger.debug("new user recorded")
context.JK.GA.virtualPageView('/redeemInBrowserNewUser');
}
})
app.user().done(function(user) {
if(!user.first_downloaded_client_at) {
$downloadApplicationLink.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.attr('status', 'done')
}
}
function events() {
$backBtn.on('click', function(e) {
e.preventDefault();
context.location = '/client#/jamtrackBrowse'
})
}
function initialize() {
var screenBindings = {
'beforeShow': beforeShow,
'afterShow': afterShow,
'beforeHide': beforeHide
};
app.bindScreen('redeemComplete', screenBindings);
$screen = $("#redeemCompleteScreen");
$templatePurchasedJamTrack = $('#template-purchased-jam-track');
$thanksPanel = $screen.find(".thanks-panel");
$jamTrackInBrowser = $screen.find(".jam-tracks-in-browser");
$jamTrackInClient = $screen.find(".jam-tracks-in-client");
$purchasedJamTrack = $thanksPanel.find(".thanks-detail.purchased-jam-track");
$purchasedJamTrackHeader = $purchasedJamTrack.find(".purchased-jam-track-header");
$purchasedJamTracks = $purchasedJamTrack.find(".purchased-list")
$backBtn = $screen.find('.back');
$downloadApplicationLink = $screen.find('.download-jamkazam-wrapper');
$noPurchasesPrompt = $screen.find('.no-purchases-prompt')
if ($screen.length == 0) throw "$screen must be specified";
events();
}
this.initialize = initialize;
return this;
}
})
(window, jQuery);

View File

@ -0,0 +1,245 @@
(function(context,$) {
"use strict";
context.JK = context.JK || {};
context.JK.RedeemSignUpScreen = function(app) {
var logger = context.JK.logger;
var rest = context.JK.Rest();
var $screen = null;
var $signupForm = null;
var $self = $(this);
var $email = null;
var $password = null;
var $firstName = null;
var $lastName = null;
var $signupBtn = null;
var $inputElements = null;
var $contentHolder = null;
var $btnNext = null;
var $btnFacebook = null;
var $termsOfServiceL = null;
var $termsOfServiceR = null;
var shoppingCartItem = null;
var $jamtrackName = null;
var $signinLink = null;
function beforeShow(data) {
renderLoggedInState();
}
function afterShow(data) {
}
function renderLoggedInState(){
if(isLoggedIn()) {
$contentHolder.removeClass('not-signed-in').addClass('signed-in')
}
else {
context.JK.Tracking.redeemSignupTrack(app)
$jamtrackName.text('')
$contentHolder.addClass('hidden')
$contentHolder.removeClass('signed-in').addClass('not-signed-in')
// now check that the user has one, and only one, free jamtrack in their shopping cart.
rest.getShoppingCarts()
.done(handleShoppingCartResponse)
.fail(app.ajaxError);
}
}
function isLoggedIn() {
return !!context.JK.currentUserId;
}
function events() {
$btnFacebook.on('click', onClickSignupFacebook)
$signupForm.on('submit', signup)
$signupBtn.on('click', signup)
$signinLink.on('click', onSignin);
}
function handleShoppingCartResponse(carts) {
if(carts.length == 0) {
// nothing is in the user's shopping cart. They shouldn't be here.
logger.error("invalid access of redeemJamTrack page")
window.location = '/client#/jamtrackBrowse'
}
else if(carts.length > 1) {
// the user has multiple items in their shopping cart. They shouldn't be here.
logger.error("invalid access of redeemJamTrack page")
window.location = '/client#/jamtrackBrowse'
}
else {
var item = carts[0];
if(item.product_info.free) {
// ok, we have one, free item. save it for
shoppingCartItem = item;
$jamtrackName.text('"' + shoppingCartItem.product_info.name + '"')
$contentHolder.removeClass('hidden')
}
else {
// the user has a non-free, single item in their basket. They shouldn't be here.
logger.error("invalid access of redeemJamTrack page")
window.location = '/client#/jamtrackBrowse'
}
}
var $latestCartHtml = "";
var any_in_us = false
context._.each(carts, function(cart) {
if(cart.product_info.sales_region == 'United States') {
any_in_us = true
}
})
}
function onClickSignupFacebook(e) {
// tos must already be clicked
$btnFacebook.addClass('disabled')
var $field = $termsOfServiceL.closest('.field')
$field.find('.error-text').remove()
logger.debug("field, ", $field, $termsOfServiceL)
if($termsOfServiceL.is(":checked")) {
rest.createSignupHint({redirect_location: '/client#/redeemComplete'})
.done(function() {
// send the user on to facebook signin
window.location = $btnFacebook.attr('href');
})
.fail(function() {
app.notify({text:"Facebook Signup is not working properly"});
})
.always(function() {
$btnFacebook.removeClass('disabled')
})
}
else {
$field.addClass("error").addClass("transparent");
$field.append("<ul class='error-text'><li>must be accepted</li></ul>");
}
return false;
}
function onSuccessfulSignin() {
// the user has signed in;
// move all shopping carts from the anonymous user to the signed in user
/*rest.portOverCarts()
.done(function() {
logger.debug("ported over carts")
window.location = '/client#/redeemComplete'
})
.fail(function() {
window.location.reload();
})
*/
window.location = '/client#/redeemComplete'
}
function onSignin() {
app.layout.showDialog('signin-dialog', {redirect_to:onSuccessfulSignin});
return false;
}
function signup() {
if($signupBtn.is('.disabled')) {
return false;
}
// clear out previous errors
$signupForm.find('.field.error').removeClass('error')
$signupForm.find('ul.error-text').remove()
var email = $email.val();
var password = $password.val();
var first_name = $firstName.val();
var last_name = $lastName.val();
var terms = $termsOfServiceR.is(':checked')
$signupBtn.text('TRYING...').addClass('disabled')
rest.signup({email: email, password: password, first_name: first_name, last_name: last_name, terms:terms})
.done(function(response) {
window.location = '/client#/redeemComplete'
window.location.reload()
})
.fail(function(jqXHR) {
if(jqXHR.status == 422) {
var response = JSON.parse(jqXHR.responseText)
if(response.errors) {
var $errors = context.JK.format_errors('first_name', response);
if ($errors) $firstName.closest('.field').addClass('error').append($errors);
$errors = context.JK.format_errors('last_name', response);
if ($errors) $lastName.closest('.field').addClass('error').append($errors);
$errors = context.JK.format_errors('password', response);
if ($errors) $password.closest('.field').addClass('error').append($errors);
var $errors = context.JK.format_errors('email', response);
if ($errors) $email.closest('.field').addClass('error').append($errors);
var $errors = context.JK.format_errors('terms_of_service', response);
if ($errors) $termsOfServiceR.closest('.field').addClass('error').append($errors);
}
else {
app.notify({title: 'Unknown Signup Error', text: jqXHR.responseText})
}
}
else {
app.notifyServerError(jqXHR, "Unable to Sign Up")
}
})
.always(function() {
$signupBtn.text('SIGNUP').removeClass('disabled')
})
return false;
}
function initialize() {
var screenBindings = {
'beforeShow': beforeShow,
'afterShow': afterShow
};
app.bindScreen('redeemSignup', screenBindings);
$screen = $("#redeemSignupScreen");
$signupForm = $screen.find(".signup-form");
$signupBtn = $signupForm.find('.signup-submit');
$email = $signupForm.find('input[name="email"]');
$password = $signupForm.find('input[name="password"]');
$firstName = $signupForm.find('input[name="first_name"]');
$lastName = $signupForm.find('input[name="last_name"]');
$inputElements = $signupForm.find('.input-elements');
$contentHolder = $screen.find('.content-holder');
$btnFacebook = $screen.find('.signup-facebook')
$termsOfServiceL = $screen.find('.left-side .terms_of_service input[type="checkbox"]')
$termsOfServiceR = $screen.find('.right-side .terms_of_service input[type="checkbox"]')
$jamtrackName = $screen.find('.jamtrack-name');
$signinLink = $screen.find('.signin')
if($screen.length == 0) throw "$screen must be specified";
events();
}
this.initialize = initialize;
return this;
}
})(window,jQuery);

View File

@ -501,6 +501,7 @@
rest.openJamTrack({id: context.JK.CurrentSessionModel.id(), jam_track_id: jamTrack.id}) rest.openJamTrack({id: context.JK.CurrentSessionModel.id(), jam_track_id: jamTrack.id})
.done(function(response) { .done(function(response) {
// now actually load the jamtrack // now actually load the jamtrack
context.JK.CurrentSessionModel.updateSession(response);
loadJamTrack(jamTrack); loadJamTrack(jamTrack);
}) })
.fail(function(jqXHR) { .fail(function(jqXHR) {

View File

@ -156,6 +156,7 @@
} }
// did I open up the current JamTrack? // did I open up the current JamTrack?
function selfOpenedJamTracks() { function selfOpenedJamTracks() {
logger.debug("currentSession", currentSession)
return currentSession && (currentSession.jam_track_initiator_id == context.JK.currentUserId) return currentSession && (currentSession.jam_track_initiator_id == context.JK.currentUserId)
} }

View File

@ -30,23 +30,35 @@
function proceedCheckout(e) { function proceedCheckout(e) {
e.preventDefault(); e.preventDefault();
logger.debug("proceedCheckout") if (context.JK.currentUserFreeJamTrack) {
if (!context.JK.currentUserId) { if(context.JK.currentUserId) {
logger.debug("proceeding to signin screen because there is no user") logger.debug("proceeding to redeem complete screen because user has a free jamtrack and is logged in")
window.location = '/client#/checkoutSignin'; window.location = '/client#/redeemComplete'
}
else {
var user = app.currentUser();
if(user.has_recurly_account && user.reuse_card) {
logger.debug("proceeding to checkout order screen because we have card info already")
window.location = '/client#/checkoutOrder';
} }
else { else {
logger.debug("proceeding to checkout payment screen because we do not have card info") logger.debug("proceeding to redeem signup screen because user has a free jamtrack and is not logged in")
window.location = '/client#/checkoutPayment'; window.location = '/client#/redeemSignup'
} }
} }
else {
if (!context.JK.currentUserId) {
logger.debug("proceeding to signin screen because there is no user")
window.location = '/client#/checkoutSignin';
}
else {
var user = app.currentUser();
if(user.has_recurly_account && user.reuse_card) {
logger.debug("proceeding to checkout order screen because we have card info already")
window.location = '/client#/checkoutOrder';
}
else {
logger.debug("proceeding to checkout payment screen because we do not have card info")
window.location = '/client#/checkoutPayment';
}
}
}
} }
function removeCart(e) { function removeCart(e) {

View File

@ -9,24 +9,32 @@
var logger = context.JK.logger; var logger = context.JK.logger;
var $page = null; var $page = null;
var $jamtrack_name = null; var $jamtrack_name = null;
var $jamtrack_band = null;
var $previews = null; var $previews = null;
var $jamTracksButton = null; var $jamTracksButton = null;
var $genericHeader = null; var $genericHeader = null;
var $individualizedHeader = null; var $individualizedHeader = null;
var $ctaJamTracksButton = null;
function fetchJamTrack() { function fetchJamTrack() {
rest.getJamTrack({plan_code: gon.jam_track_plan_code}) rest.getJamTrackWithArtistInfo({plan_code: gon.jam_track_plan_code})
.done(function (jam_track) { .done(function (jam_track) {
logger.debug("jam_track", jam_track) logger.debug("jam_track", jam_track)
if(!gon.just_previews) { if(!gon.just_previews) {
if (gon.generic) { if (gon.generic) {
$genericHeader.removeClass('hidden'); $genericHeader.removeClass('hidden');
$jamTracksButton.attr('href', '/client#/jamtrackBrowse')
$jamTracksButton.removeClass('hidden').text("Check out all 100+ JamTracks")
} }
else { else {
$individualizedHeader.removeClass('hidden') $individualizedHeader.removeClass('hidden')
$jamtrack_name.text(jam_track.name); $jamtrack_name.text('"' + jam_track.name + '"');
$jamtrack_band.text(jam_track.original_artist)
$jamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrackBrowse') $jamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrackBrowse')
$jamTracksButton.removeClass('hidden').text("Preview all " + jam_track.band_jam_track_count + " of our " + jam_track.original_artist + " JamTracks")
$ctaJamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrackBrowse')
} }
} }
@ -36,24 +44,29 @@
$previews.append($element); $previews.append($element);
new context.JK.JamTrackPreview(app, $element, jam_track, track, {master_shows_duration: false, color:'black'}) new context.JK.JamTrackPreview(app, $element, jam_track, track, {master_shows_duration: false, color:'black', master_adds_line_break: true})
}) })
$previews.append('<br clear = "all" />') $previews.append('<br clear = "all" />')
}) })
.fail(function () { .fail(function () {
app.notify({title: 'Unable to fetch JamTrack', text: "Please refresh the page or try again later."}) app.notify({title: 'Unable to fetch JamTrack', text: "Please refresh the page or try again later."})
}) })
} }
function initialize() { function initialize() {
$page = $('body') $page = $('body')
$jamtrack_name = $page.find('.jamtrack_name') $jamtrack_name = $page.find('.jamtrack_name')
$jamtrack_band = $page.find('.jamtrack_band')
$previews = $page.find('.previews') $previews = $page.find('.previews')
$jamTracksButton = $page.find('.browse-jamtracks-wrapper .white-bordered-button') $jamTracksButton = $page.find('.browse-jamtracks')
$ctaJamTracksButton = $page.find('.cta-free-jamtrack');
$genericHeader = $page.find('h1.generic') $genericHeader = $page.find('h1.generic')
$individualizedHeader = $page.find('h1.individualized') $individualizedHeader = $page.find('h1.individualized')
context.JK.Tracking.adTrack(app)
fetchJamTrack(); fetchJamTrack();
} }

View File

@ -13,27 +13,28 @@
var $jamTrackNoun = null; var $jamTrackNoun = null;
var $previews = null; var $previews = null;
var $jamTracksButton = null; var $jamTracksButton = null;
var $jamtrack_band = null;
var $checkItOut = null; var $checkItOut = null;
var $ctaJamTracksButton = null;
function fetchJamTrack() { function fetchJamTrack() {
rest.getJamTrackWithArtistInfo({plan_code: gon.jam_track_plan_code}) rest.getJamTrackWithArtistInfo({plan_code: gon.jam_track_plan_code})
.done(function (jam_track) { .done(function (jam_track) {
logger.debug("jam_track", jam_track) logger.debug("jam_track", jam_track)
$jamTrackBandInfo.text(jam_track.band_jam_track_count + ' ' + jam_track.original_artist); $jamtrack_band.text(jam_track.original_artist)
$jamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrackBrowse') $jamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrackBrowse')
$jamTracksButton.removeClass('hidden').text("Preview all " + jam_track.band_jam_track_count + " of our " + jam_track.original_artist + " JamTracks")
$ctaJamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrackBrowse')
if(jam_track.band_jam_track_count == 1) {
$jamTrackNoun.text('JamTrack')
$checkItOut.text(', Check It Out!')
}
context._.each(jam_track.tracks, function (track) { context._.each(jam_track.tracks, function (track) {
var $element = $('<div class="jam-track-preview-holder"></div>') var $element = $('<div class="jam-track-preview-holder"></div>')
$previews.append($element); $previews.append($element);
new context.JK.JamTrackPreview(app, $element, jam_track, track, {master_shows_duration: false, color:'black'}) new context.JK.JamTrackPreview(app, $element, jam_track, track, {master_shows_duration: false, color:'black', master_adds_line_break:true})
}) })
$previews.append('<br clear = "all" />') $previews.append('<br clear = "all" />')
@ -48,10 +49,11 @@
$page = $('body') $page = $('body')
$jamTrackBandInfo = $page.find('.jamtrack_band_info') $jamTrackBandInfo = $page.find('.jamtrack_band_info')
$previews = $page.find('.previews') $previews = $page.find('.previews')
$jamTracksButton = $page.find('.browse-jamtracks-wrapper .white-bordered-button') $jamtrack_band = $page.find('.jamtrack_band')
$jamTrackNoun = $page.find('.jamtrack_noun') $jamTracksButton = $page.find('.browse-jamtracks')
$checkItOut = $page.find('.check-it-out') $ctaJamTracksButton = $page.find('.cta-free-jamtrack');
context.JK.Tracking.adTrack(app)
fetchJamTrack(); fetchJamTrack();
} }

View File

@ -20,21 +20,26 @@
var $rememberMe = null; var $rememberMe = null;
var useAjax = false; var useAjax = false;
var EVENTS = context.JK.EVENTS; var EVENTS = context.JK.EVENTS;
var argRedirectTo = null;
function reset() {
function reset(_redirectTo) {
argRedirectTo = _redirectTo;
clear();
}
function clear() {
$signinForm.removeClass('login-error') $signinForm.removeClass('login-error')
$email.val(''); $email.val('');
$password.val(''); $password.val('');
$rememberMe.attr('checked', 'checked') $rememberMe.attr('checked', 'checked')
} }
function login() { function login() {
var email = $email.val(); var email = $email.val();
var password = $password.val(); var password = $password.val();
var rememberMe = $rememberMe.is(':checked') var rememberMe = $rememberMe.is(':checked')
reset(); clear();
$signinBtn.text('TRYING...'); $signinBtn.text('TRYING...');
@ -42,6 +47,15 @@
.done(function() { .done(function() {
//app.layout.closeDialog('signin-dialog') //app.layout.closeDialog('signin-dialog')
if(argRedirectTo) {
if(argRedirectTo instanceof Function) {
argRedirectTo();
}
else {
window.location.href = argRedirectTo;
}
return;
}
var redirectTo = $.QueryString['redirect-to']; var redirectTo = $.QueryString['redirect-to'];
if(redirectTo) { if(redirectTo) {
logger.debug("redirectTo:" + redirectTo); logger.debug("redirectTo:" + redirectTo);
@ -68,7 +82,7 @@
function events() { function events() {
$signinCancelBtn.click(function(e) { $signinCancelBtn.click(function(e) {
app.layout.closeDialog('signin-dialog'); app.layout.closeDialog('signin-dialog', true);
e.stopPropagation(); e.stopPropagation();
return false; return false;
}); });

View File

@ -0,0 +1,81 @@
$ = jQuery
context = window
context.JK ||= {};
class Tracking
constructor: () ->
@logger = context.JK.logger
@rest = new context.JK.Rest();
adTrack: (app) =>
utmSource = $.QueryString['utm_source']
if utmSource == 'facebook-ads' || utmSource == 'google-ads' || utmSource == 'twitter-ads' || utmSource == 'affiliate' || utmSource == 'pr'
if !context.jamClient.IsNativeClient()
if context.JK.currentUserId?
app.user().done( (user) =>
# relative to 1 day ago (24 * 60 * 60 * 1000)
if new Date(user.created_at).getTime() < new Date().getTime() - 86400000
@logger.debug("existing user recorded")
context.JK.GA.virtualPageView('/landing/jamtracks/existing-user/');
else
@logger.debug("new user recorded")
context.JK.GA.virtualPageView('/landing/jamtracks/new-user/')
)
else
@logger.debug("new user recorded")
context.JK.GA.virtualPageView('/landing/jamtracks/new-user/')
else
@logger.debug("existing user recorded")
context.JK.GA.virtualPageView('/landing/jamtracks/existing-user/');
jamtrackBrowseTrack: (app) =>
if context.JK.currentUserId?
app.user().done( (user) =>
if context.jamClient.IsNativeClient()
@logger.debug("client user recorded")
context.JK.GA.virtualPageView('/client#/jamtrackBrowse/user-in-app')
else
if new Date(user.created_at).getTime() < new Date().getTime() - 86400000
@logger.debug("existing user recorded")
context.JK.GA.virtualPageView('/client#/jamtrackBrowse/existing-user')
else
@logger.debug("existing new recorded")
context.JK.GA.virtualPageView('/client#/jamtrackBrowse/new-user')
)
else
if context.jamClient.IsNativeClient()
@logger.debug("client user recorded")
context.JK.GA.virtualPageView('/client#/jamtrackBrowse/user-in-app')
else
@logger.debug("existing new recorded")
context.JK.GA.virtualPageView('/client#/jamtrackBrowse/new-user')
redeemSignupTrack: (app) =>
if context.JK.currentUserId?
app.user().done( (user) =>
if context.jamClient.IsNativeClient()
@logger.debug("client user recorded")
context.JK.GA.virtualPageView('/client#/redeemSignup/user-in-app')
else
if new Date(user.created_at).getTime() < new Date().getTime() - 86400000
@logger.debug("existing existing recorded")
context.JK.GA.virtualPageView('/client#/redeemSignup/existing-user')
else
@logger.debug("existing new recorded")
context.JK.GA.virtualPageView('/client#/redeemSignup/new-user')
)
else
if context.jamClient.IsNativeClient()
@logger.debug("client user recorded")
context.JK.GA.virtualPageView('/client#/redeemSignup/user-in-app')
else
@logger.debug("existing new recorded")
context.JK.GA.virtualPageView('/client#/redeemSignup/new-user')
context.JK.Tracking = new Tracking()

View File

@ -62,6 +62,7 @@
//= require web/session_info //= require web/session_info
//= require web/recordings //= require web/recordings
//= require web/home //= require web/home
//= require web/tracking
//= require web/individual_jamtrack //= require web/individual_jamtrack
//= require web/individual_jamtrack_band //= require web/individual_jamtrack_band
//= require fakeJamClient //= require fakeJamClient

View File

@ -62,6 +62,8 @@
*= require ./checkout_payment *= require ./checkout_payment
*= require ./checkout_order *= require ./checkout_order
*= require ./checkout_complete *= require ./checkout_complete
*= require ./redeem_signup
*= require ./redeem_complete
*= require ./genreSelector *= require ./genreSelector
*= require ./sessionList *= require ./sessionList
*= require ./searchResults *= require ./searchResults
@ -78,4 +80,5 @@
*= require users/syncViewer *= require users/syncViewer
*= require ./downloadJamTrack *= require ./downloadJamTrack
*= require ./jamTrackPreview *= require ./jamTrackPreview
*= require users/signinCommon
*/ */

View File

@ -314,30 +314,6 @@ a.gold {
color: #cc9900 !important; color: #cc9900 !important;
} }
a.arrow-up {
float:right;
margin-right:5px;
display:block;
margin-top:6px;
width: 0;
height: 0;
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-bottom: 7px solid #333;
}
a.arrow-down {
float:right;
margin-right:5px;
display:block;
margin-top:6px;
width: 0;
height: 0;
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-top: 7px solid #333;
}
.settings-session-description { .settings-session-description {
padding:10px; padding:10px;
width:200px; width:200px;
@ -537,6 +513,32 @@ ul.shortcuts {
padding:2px 8px !important; padding:2px 8px !important;
} }
a.arrow-up {
float:right;
margin-right:5px;
display:block;
margin-top:6px;
width: 0;
height: 0;
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-bottom: 7px solid #333;
}
a.arrow-down {
float:right;
margin-right:5px;
display:block;
margin-top:6px;
width: 0;
height: 0;
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-top: 7px solid #333;
}
a.arrow-up-orange { a.arrow-up-orange {
margin-left:5px; margin-left:5px;
margin-bottom:2px; margin-bottom:2px;

View File

@ -68,4 +68,10 @@
display:none; display:none;
} }
} }
.adds-line-break {
display:block;
margin-left:66px;
vertical-align: top;
}
} }

View File

@ -31,6 +31,9 @@
} }
} }
table.jamtrack-table {
table-layout:fixed;
}
.jamtrack-content { .jamtrack-content {
text-align: center; text-align: center;
border: 1px solid #222222; border: 1px solid #222222;
@ -47,22 +50,41 @@
text-align: left; text-align: left;
} }
th.jamtrack-detail {
padding:6px;
}
th.jamtrack-tracks {
padding:6px;
}
th.jamtrack-action {
padding:6px;
text-align:center;
}
td.jamtrack-action {
padding:0;
position:relative;
}
.jamtrack-detail { .jamtrack-detail {
@include border_box_sizing; @include border_box_sizing;
width: 35%; width: 25%;
padding: 10px 0 0 10px; padding: 10px 0 0 10px;
.detail-label { .detail-label {
width: 40%; width: 80px;
float: left; float: left;
margin-top: 5px; margin-top: 5px;
font-weight: 400; font-weight: 400;
font-size: 11px; font-size: 11px;
clear:both;
} }
.detail-value { .detail-value {
width: 50%;
float: left; float: left;
margin-top: 5px; margin-top: 5px;
margin-bottom:15px;
font-size:11px; font-size:11px;
} }
@ -84,12 +106,37 @@
margin-right: 5px; margin-right: 5px;
padding-top: 5px; padding-top: 5px;
vertical-align: bottom; vertical-align: bottom;
color:#fc0;
.arrow-down {
float:none;
margin-left:5px;
margin-top:0;
margin-right:0;
border-top: 4px solid #fc0;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
display:inline-block;
}
.arrow-up {
float:none;
margin-right:0;
margin-left:5px;
margin-bottom:2px;
border-bottom: 4px solid #fc0;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
display:inline-block;
}
} }
.jamtrack-tracks { .jamtrack-tracks {
padding-bottom:30px;
position:relative;
@include border_box_sizing; @include border_box_sizing;
width: 45%; width:55%;
padding: 10px 0px; //padding: 10px 0px;
.tracks-caption { .tracks-caption {
margin-top: 5px; margin-top: 5px;
margin-bottom: 10px; margin-bottom: 10px;
@ -117,12 +164,31 @@
.detail-arrow { .detail-arrow {
position:absolute;
float: right; float: right;
margin-right: 10px; margin-right: 10px;
bottom:14px;
left:12px;
} }
.jamtrack-name {
font-size:16px;
white-space: normal;
}
.jamtrack-original-artist {
font-size:16px;
margin-top:10px;
margin-bottom:5px;
white-space: normal;
}
.jamtrack-track { .jamtrack-track {
margin:0 0 8px 7px; float:left;
padding:0 0 8px 7px;
width: 250px;
@include border_box_sizing;
&.hidden { &.hidden {
display:none; display:none;
@ -131,14 +197,23 @@
.jam-track-preview { .jam-track-preview {
font-size:11px; font-size:11px;
white-space:nowrap;
} }
.jamtrack-action { .jamtrack-action {
@include border_box_sizing; @include border_box_sizing;
width: 20%; width: 20%;
// padding: 0px 0px;
text-align: center; text-align: center;
// vertical-align: top;
.jamtrack-action-container {
display: flex;
align-items: center;
justify-content: center;
position:absolute;
height:100%;
width:100%;
}
.play-button { .play-button {
margin-top: 5px; margin-top: 5px;
} }
@ -149,6 +224,7 @@
&.free { &.free {
margin-top:0; margin-top:0;
display:none;
.free-state { .free-state {
font-size: 11px; font-size: 11px;
margin-top: 5px; margin-top: 5px;

View File

@ -0,0 +1,126 @@
@import "client/common.css.scss";
#redeemCompleteScreen {
p {
font-size:14px;
margin:0;
}
.order-prompt {
color:white;
line-height:125%;
}
h2 {
color:white;
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;
&.purchased-jam-track-header {
background-color:#4d4d4d;
}
}
.action-bar {
margin-top:20px;
}
#checkout-info-help {
margin-right:1px;
}
.checkout-complete-wrapper {
padding:50px;
}
.why-download {
margin-top:20px;
}
.no-purchases-prompt {
}
.jam-tracks-in-browser {
}
.thanks-panel {
span.notice {
font-style:italic;
font-size:12px;
}
br.purchase-downloads {
clear:both;
margin-bottom:20px;
}
.download-jamkazam {
color:$ColorLink;
border-radius: 4px;
border-style:solid;
border-color:#AAA;
border-width:1px;
padding:10px;
margin-top:20px;
display:inline-block;
}
.download-jamkazam-wrapper {
text-align:center;
display:block;
margin-top:35px;
&.hidden {
display:none;
}
}
.thanks-detail.purchased-jam-track {
margin-top:20px;
.purchased-jam-track-header {
font-size: 15px;
margin-bottom:10px;
span {
display:none;
}
&[status="in-progress"] {
span.in-progress-msg {
display:inline;
}
}
&[status="done"] {
span.done-msg {
display:inline;
}
}
}
ul.purchased-list {
float:left;
margin:20px 100px 0 20px;
li {
margin:0;
}
}
.download-jamtrack {
width:auto;
vertical-align: middle; // to make bullets mid-align when error shows
}
}
}
}

View File

@ -0,0 +1,247 @@
@import "client/common.css.scss";
#redeemSignupScreen {
.redeem-signup {
padding:30px;
}
.g-recaptcha {
//transform:scale(0.80);
//transform-origin:0 0;
}
.jamtrack-name {
font-weight:bold;
}
.content-holder {
//margin-top:40px;
color:$ColorTextTypical;
&.signed-in {
.left-side, .right-side {
display:none;
}
.not-signed-in.prompt {
display:none;
}
}
&.not-signed-in {
.already-signed-in {
display:none;
}
}
}
.not-signed-in.prompt {
text-align:center;
}
.already-signed-in {
text-align:center;
h3 {
text-align:center;
}
}
a.forgot-password {
position:absolute;
top:0;
left:0;
font-size:10px;
}
.error-text {
margin:0;
li {
margin-bottom:5px;
}
}
.signup-submit {
margin-right:2px;
}
.signup-facebook {
text-align:center;
}
.signup-later-prompt {
margin:30px 0 30px 0;
text-align:center;
}
.signup-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%;
}
.login-error {
div.actions {
margin-top:10px;
}
.field {
background-color: #330000;
border: 1px solid #990000;
padding:8px;
}
}
.field {
display:block;
@include border_box_sizing;
padding:6px 8px;
margin:0px -8px;
white-space: nowrap;
&.error {
background-color:#300;
border: solid 1px #900;
li {
list-style:none;
}
}
}
.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:left;
font-size:16px;
font-weight:700;
margin-bottom:15px;
}
input {
@include border_box_sizing;
}
.btnNext {
margin:0 auto;
}
.left-side, .right-side {
margin-top:50px;
}
.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 60px;
margin-bottom:20px;
.actions {
width:60%;
max-width:300px;
@include border_box_sizing;
position:relative;
float:right;
}
.terms_of_service {
text-align:center;
label {
display:inline;
}
font-size:11px;
}
}
a.signin {
text-decoration: underline;
font-size:12px;
margin-left:15px;
}
.left-side-content {
@include border_box_sizing;
float:right;
text-align:center;
}
.right-side {
float:left;
width:50%;
@include border_box_sizing;
padding: 0 60px;
.actions {
margin-top:11px;
margin-left:-8px;
white-space: nowrap;
input {
width:auto;
}
}
input {
width:80%;
max-width:300px;
}
label {
display:inline-block;
width:80px;
text-align:left;
}
.terms_of_service {
input {
width: auto;
margin-left: -2px;
}
label {
display:inline;
}
font-size:11px;
}
}
.facebook-prompt {
margin:40px 0 10px 0;
float:right;
text-align:left;
width:249px;
@include border_box_sizing;
}
}

View File

@ -16,17 +16,20 @@ body.web.landing_jamtrack.individual_jamtrack {
width:90%; width:90%;
} }
.prompt {
margin-top:10px;
}
.jam-track-preview-holder { .jam-track-preview-holder {
margin-bottom: 7px; margin-bottom: 7px;
float: left;
&[data-track-type="Master"] { &[data-track-type="Master"] {
width: 100%; width: 100%;
} }
&[data-track-type="Track"] { &[data-track-type="Track"] {
width: 50%; width: 100%;
} }
} }
} }

View File

@ -16,17 +16,20 @@ body.web.landing_jamtrack.individual_jamtrack_band {
width:90%; width:90%;
} }
.prompt {
margin-top:10px;
}
.jam-track-preview-holder { .jam-track-preview-holder {
margin-bottom: 7px; margin-bottom: 7px;
float: left;
&[data-track-type="Master"] { &[data-track-type="Master"] {
width: 100%; width: 100%;
} }
&[data-track-type="Track"] { &[data-track-type="Track"] {
width: 50%; width: 100%;
} }
} }
} }

View File

@ -2,6 +2,28 @@
body.web.landing_page { body.web.landing_page {
div.cta-free-jamtrack {
top: 32px;
position: absolute;
left: 383px;
a {
width: 296px;
height: 57px;
}
.value-indicator {
position:absolute;
left: 311px;
top: 19px;
width:80px;
}
img {
height:100%;
width:100%;
}
}
.two_by_two { .two_by_two {
h1 { h1 {
@ -29,6 +51,41 @@ body.web.landing_page {
} }
} }
.one_by_two {
h1 {
margin:0 0 5px;
padding:7px 0;
display:inline-block;
&.hidden {
display:none;
}
}
.row {
@include border_box_sizing;
&:nth-of-type(1) {
padding:20px 0 0 0;
}
.column {
@include border_box_sizing;
&:nth-of-type(1) {
width:35%;
}
&:nth-of-type(2) {
width:65%;
}
}
}
.video-container {
width:100%;
padding-bottom:56.25%;
}
}
&.landing_jamtrack, &.landing_product { &.landing_jamtrack, &.landing_product {
.landing-tag { .landing-tag {

View File

@ -8,6 +8,10 @@
height:auto; height:auto;
} }
strong {
font-weight:bold;
}
label { label {
margin-bottom:2px; margin-bottom:2px;
} }

View File

@ -73,13 +73,25 @@ class ApiJamTracksController < ApiController
def download def download
if @jam_track_right.valid? if @jam_track_right.valid?
all_fingerprint = params[:all_fp]
running_fingerprint = params[:running_fp]
if Rails.application.config.guard_against_fraud
error = @jam_track_right.guard_against_fraud(current_user, {all:all_fingerprint, running: running_fingerprint}, request.remote_ip)
if error
log.warn("potential fraud detected: #{error}")
render :json => { :message => error }, :status => 403
return
end
end
sample_rate = params[:sample_rate].nil? ? nil : params[:sample_rate].to_i sample_rate = params[:sample_rate].nil? ? nil : params[:sample_rate].to_i
if @jam_track_right && @jam_track_right.ready?(sample_rate) if @jam_track_right && @jam_track_right.ready?(sample_rate)
@jam_track_right.update_download_count @jam_track_right.update_download_count
@jam_track_right.last_downloaded_at = Time.now now = Time.now
if @jam_track_right.first_downloaded_at.nil? @jam_track_right.last_downloaded_at = now
@jam_track_right.first_downloaded_at = Time.now @jam_track_right.first_downloaded_at = now if @jam_track_right.first_downloaded_at.nil?
end
@jam_track_right.save! @jam_track_right.save!
redirect_to @jam_track_right.sign_url(120, sample_rate) redirect_to @jam_track_right.sign_url(120, sample_rate)
else else
@ -92,6 +104,18 @@ class ApiJamTracksController < ApiController
end end
def enqueue def enqueue
fingerprint = params[:fingerprint]
if Rails.application.config.guard_against_fraud
error = @jam_track_right.guard_against_fraud(current_user, fingerprint, request.remote_ip)
if error
log.warn("potential fraud detected: #{error}")
render :json => { :message => error }, :status => 403
return
end
end
sample_rate = params[:sample_rate].nil? ? nil : params[:sample_rate].to_i sample_rate = params[:sample_rate].nil? ? nil : params[:sample_rate].to_i
enqueued = @jam_track_right.enqueue_if_needed(sample_rate) enqueued = @jam_track_right.enqueue_if_needed(sample_rate)
log.debug("jamtrack #{enqueued ? "ENQUEUED" : "NOT ENQUEUED"}: jam_track_right=#{@jam_track_right.id} sample_rate=#{sample_rate} ") log.debug("jamtrack #{enqueued ? "ENQUEUED" : "NOT ENQUEUED"}: jam_track_right=#{@jam_track_right.id} sample_rate=#{sample_rate} ")
@ -119,7 +143,7 @@ class ApiJamTracksController < ApiController
# jamtracks come in the form id-44 or id-48, so we need to do a little extra parsing # jamtracks come in the form id-44 or id-48, so we need to do a little extra parsing
puts "#{jamtracks.inspect}"
jamtrack_ids = Set.new jamtrack_ids = Set.new
jamtracks_fq_ids = Set.new jamtracks_fq_ids = Set.new
jamtracks.each do |jamtrack| jamtracks.each do |jamtrack|
@ -131,8 +155,6 @@ class ApiJamTracksController < ApiController
end end
end end
puts "#{jamtrack_ids.inspect} #{jamtracks_fq_ids.inspect}"
@jam_tracks = JamTrackRight.list_keys(current_user, jamtrack_ids) @jam_tracks = JamTrackRight.list_keys(current_user, jamtrack_ids)
@jamtracks_fq_ids = jamtracks_fq_ids @jamtracks_fq_ids = jamtracks_fq_ids
end end

View File

@ -25,26 +25,20 @@ class ApiRecurlyController < ApiController
# keep reuse card up-to-date # keep reuse card up-to-date
User.where(id: current_user.id).update_all(reuse_card: params[:reuse_card_next_time]) User.where(id: current_user.id).update_all(reuse_card: params[:reuse_card_next_time])
else else
options = { options = {
remote_ip: request.remote_ip,
first_name: billing_info[:first_name], first_name: billing_info[:first_name],
last_name: billing_info[:last_name], last_name: billing_info[:last_name],
email: params[:email], email: params[:email],
password: params[:password], password: params[:password],
password_confirmation: params[:password], password_confirmation: params[:password],
terms_of_service: terms_of_service, 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]}, 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 reuse_card: reuse_card_next_time
} }
options = User.musician_defaults(request.remote_ip, ApplicationHelper.base_uri(request) + "/confirm", any_user, options)
user = UserManager.new.signup(options) user = UserManager.new.signup(options)
if user.errors.any? if user.errors.any?
@ -142,6 +136,7 @@ class ApiRecurlyController < ApiController
if error if error
render json: {errors: {message: error}}, :status => 404 render json: {errors: {message: error}}, :status => 404
else else
set_purchased_jamtrack_cookie
render :json => response, :status => 200 render :json => response, :status => 200
end end
rescue RecurlyClientError => x rescue RecurlyClientError => x

View File

@ -51,6 +51,15 @@ class ApiShoppingCartsController < ApiController
respond_with responder: ApiResponder, :status => 204 respond_with responder: ApiResponder, :status => 204
end end
# take all shopping carts from anonymous user and copy them to logged in user
def port
if current_user && anonymous_user
ShoppingCart.port(current_user, anonymous_user)
else
render :json => {message: 'one or both users not available'}, :status => 403
end
end
def clear_all def clear_all
any_user.destroy_all_shopping_carts any_user.destroy_all_shopping_carts
render :json=>{}, :status=>200 render :json=>{}, :status=>200

View File

@ -43,9 +43,46 @@ class ApiUsersController < ApiController
respond_with @profile, responder: ApiResponder, :status => 200 respond_with @profile, responder: ApiResponder, :status => 200
end end
# in other words, a minimal signup
def create
# today, this only accepts a minimal registration; it could be made to take in more if we wanted
signup_hint = nil
if anonymous_user
signup_hint = anonymous_user.signup_hint
if signup_hint && signup_hint.jam_track.nil?
signup_hint = nil # it doesn't make sense to pass in signup hints that are not free jam-track centric (at least, not today)
end
end
# recaptcha_response: params['g-recaptcha-response']
options = {
first_name: params[:first_name],
last_name: params[:last_name],
email: params[:email],
password: params[:password],
password_confirmation: params[:password],
terms_of_service: params[:terms],
location: {:country => nil, :state => nil, :city => nil},
signup_hint: signup_hint
}
options = User.musician_defaults(request.remote_ip, ApplicationHelper.base_uri(request) + "/confirm", any_user, options)
@user = UserManager.new.signup(options)
if @user.errors.any?
respond_with_model(@user)
else
sign_in @user
new_user(@user, signup_hint) # sets a cookie used for GA analytics (one-time new user stuff in JavaScript)
respond_with_model(@user, new: true, location: lambda { return api_user_detail_url(@user.id) })
end
end
def profile_save def profile_save
end end
def update def update
@user = User.find(params[:id]) @user = User.find(params[:id])

View File

@ -35,6 +35,7 @@ class ApplicationController < ActionController::Base
cookies.permanent[:user_uuid] = SecureRandom.uuid unless cookies[:user_uuid] cookies.permanent[:user_uuid] = SecureRandom.uuid unless cookies[:user_uuid]
end end
private private
def add_user_info_to_bugsnag(notif) def add_user_info_to_bugsnag(notif)
# Add some app-specific data which will be displayed on a custom # Add some app-specific data which will be displayed on a custom

View File

@ -16,6 +16,7 @@ class ClientsController < ApplicationController
return return
end end
@in_client_page = true
@minimal_curtain = Rails.application.config.minimal_curtain @minimal_curtain = Rails.application.config.minimal_curtain
gon.recurly_tax_estimate_jam_track_plan = Rails.application.config.recurly_tax_estimate_jam_track_plan gon.recurly_tax_estimate_jam_track_plan = Rails.application.config.recurly_tax_estimate_jam_track_plan
render :layout => 'client' render :layout => 'client'

View File

@ -67,6 +67,8 @@ class LandingsController < ApplicationController
end end
def individual_jamtrack def individual_jamtrack
@no_landing_tag = true
@show_cta_free_jamtrack = true
@jam_track = JamTrack.find_by_plan_code("jamtrack-" + params[:plan_code]) @jam_track = JamTrack.find_by_plan_code("jamtrack-" + params[:plan_code])
gon.jam_track_plan_code = params[:plan_code] ? "jamtrack-" + params[:plan_code] : nil gon.jam_track_plan_code = params[:plan_code] ? "jamtrack-" + params[:plan_code] : nil
gon.generic = params[:generic] gon.generic = params[:generic]
@ -74,6 +76,8 @@ class LandingsController < ApplicationController
end end
def individual_jamtrack_band def individual_jamtrack_band
@no_landing_tag = true
@show_cta_free_jamtrack = true
@jam_track = JamTrack.find_by_plan_code("jamtrack-" + params[:plan_code]) @jam_track = JamTrack.find_by_plan_code("jamtrack-" + params[:plan_code])
gon.jam_track_plan_code = params[:plan_code] ? "jamtrack-" + params[:plan_code] : nil gon.jam_track_plan_code = params[:plan_code] ? "jamtrack-" + params[:plan_code] : nil

View File

@ -121,8 +121,40 @@ class SessionsController < ApplicationController
end end
fb_signup.save! fb_signup.save!
redirect_to "#{signup_path}?facebook_signup=#{fb_signup.lookup_id}" # see if we can find a SignupHint for a JamTrack; if so, bounce them back to the redeemComplete page to let them know we bought it
return
if anonymous_user
signup_hint = anonymous_user.signup_hint
if signup_hint && signup_hint.redirect_location
options = {
first_name: first_name,
last_name: last_name,
email: email,
terms_of_service: true,
location: {:country => nil, :state => nil, :city => nil},
}
options = User.musician_defaults(request.remote_ip, ApplicationHelper.base_uri(request) + "/confirm", any_user, options)
user = UserManager.new.signup(options)
if user.errors.any?
redirect_to signup_hint.redirect_location
return
end
sign_in(user)
new_user(user, signup_hint)
redirect_to signup_hint.redirect_location
return
else
redirect_to "#{signup_path}?facebook_signup=#{fb_signup.lookup_id}"
return
end
else
redirect_to "#{signup_path}?facebook_signup=#{fb_signup.lookup_id}"
return
end
end end
if current_user.nil? if current_user.nil?

View File

@ -180,23 +180,6 @@ class UsersController < ApplicationController
end end
end end
# given the current user, and any signup hint (can be nil)
# handle the final destination of the user
def handle_signup_hint(user, signup_hint, default_redirect)
redirect_url = default_redirect
if signup_hint
if signup_hint.want_jamblaster
User.where(id: user.id).update_all(want_jamblaster: true)
end
if signup_hint.redirect_location
redirect_url = signup_hint.redirect_location
end
end
redirect_url
end
def congratulations_fan def congratulations_fan
@no_user_dropdown = true @no_user_dropdown = true
render :layout => "web" render :layout => "web"
@ -383,12 +366,12 @@ class UsersController < ApplicationController
end if params[:id].present? && (service=params[:service]).present? end if params[:id].present? && (service=params[:service]).present?
service ||= 'facebook' service ||= 'facebook'
url = CGI::escape('http://www.jamkazam.com') url = CGI::escape('https://www.jamkazam.com')
txt = CGI::escape('Check out JamKazam -- Play music together over the Internet as if in the same room') txt = CGI::escape('Check out JamKazam -- Play music together over the Internet as if in the same room')
if 'twitter'==service if 'twitter'==service
url = "https://twitter.com/intent/tweet?text=#{txt}&url=#{url}" url = "https://twitter.com/intent/tweet?text=#{txt}&url=#{url}"
elsif 'facebook'==service elsif 'facebook'==service
url = "http://www.facebook.com/sharer/sharer.php?u=#{url}&t=#{txt}" url = "https://www.facebook.com/sharer/sharer.php?u=#{url}&t=#{txt}"
elsif 'google'==service elsif 'google'==service
url = "https://plus.google.com/share?url=#{url}" url = "https://plus.google.com/share?url=#{url}"
end end

View File

@ -2,6 +2,7 @@ module SessionsHelper
def sign_in(user) def sign_in(user)
set_remember_token(user) set_remember_token(user)
set_purchased_jamtrack_cookie unless user.has_redeemable_jamtrack
self.current_user = user self.current_user = user
end end
@ -21,6 +22,12 @@ module SessionsHelper
end end
end end
# should be set whenever a user logs in who has redeemed a free jamtrack, or whenever the user
def set_purchased_jamtrack_cookie
cookies.permanent[:redeemed_jamtrack] = true
end
def complete_sign_in(user, redirect=true) def complete_sign_in(user, redirect=true)
sign_in user sign_in user
@ -64,7 +71,7 @@ module SessionsHelper
def anonymous_user def anonymous_user
if anon_cookie if anon_cookie
@anonymous_user ||= AnonymousUser.new(anon_cookie) @anonymous_user ||= AnonymousUser.new(anon_cookie, cookies)
else else
nil nil
end end
@ -88,9 +95,29 @@ module SessionsHelper
redirect_location = signup_hint.redirect_location redirect_location = signup_hint.redirect_location
end end
cookies[:new_user] = { musician: user.musician, registrationType: user.user_authorization('facebook') ? 'Facebook' : 'Native', want_jamblaster: want_jamblaster, redirect_location: redirect_location }.to_json cookies[:new_user] = { musician: user.musician, registrationType: user.user_authorization('facebook') ? 'Facebook' : 'Native', want_jamblaster: want_jamblaster, redirect_location: redirect_location }.to_json
end end
# given the current user, and any signup hint (can be nil)
# handle the final destination of the user
def handle_signup_hint(user, signup_hint, default_redirect)
redirect_url = default_redirect
if signup_hint
if signup_hint.want_jamblaster
User.where(id: user.id).update_all(want_jamblaster: true)
end
if signup_hint.redirect_location
redirect_url = signup_hint.redirect_location
end
end
redirect_url
end
def current_user?(user) def current_user?(user)
user == current_user user == current_user
end end

View File

@ -11,7 +11,7 @@ end
# give back more info if the user being fetched is yourself # give back more info if the user being fetched is yourself
if @user == current_user if @user == current_user
attributes :email, :original_fpfile, :cropped_fpfile, :crop_selection, :session_settings, :show_whats_next, :show_whats_next_count, :subscribe_email, :auth_twitter, :new_notifications, :sales_count, :reuse_card, :purchased_jamtracks_count, :first_downloaded_client_at attributes :email, :original_fpfile, :cropped_fpfile, :crop_selection, :session_settings, :show_whats_next, :show_whats_next_count, :subscribe_email, :auth_twitter, :new_notifications, :sales_count, :reuse_card, :purchased_jamtracks_count, :first_downloaded_client_at, :created_at
node :geoiplocation do |user| node :geoiplocation do |user|
geoiplocation = current_user.geoiplocation geoiplocation = current_user.geoiplocation

View File

@ -0,0 +1,155 @@
<!-- Profile -->
<div layout="screen" layout-id="band/setup" layout-arg="id" class="screen secondary" id="band-setup">
<div class="content-head">
<div class="content-icon">
<%= image_tag "content/icon_bands.png", :size => "19x19" %>
</div>
<h1 id="band-setup-title">set up band</h1>
<%= render "screen_navigation" %>
</div>
<div class="content-body">
<div class="content-body-scroller">
<form id="band-setup-form">
<div style="display:block;">
<div id="band-setup-step-1" class="content-wrapper" style="padding:10px 35px 10px 35px;">
<br />
<table width="100%" cellpadding="0" cellspacing="0">
<tr>
<td valign="top" colspan="2">
<h2><span class="band-setup-text-step1">Step 1: </span>General Information</h2>
</td>
<td id="tdBandPhoto" valign="middle" rowspan="2" width="33%">
<a href="#" class="avatar-profile">
<%= image_tag "shared/avatar_generic_band.png", {:id => "band-avatar", :align=>"absmiddle", :height => 88, :width => 88 } %>
</a>
<br/><br/>
<a id="band-change-photo" href="#" class="small ml20">Upload band photo.</a><br clear="all"><br/>
</td>
</tr>
<tr>
<td id="tdBandName" valign="middle" width="33%">
<div class="field">
<label for="band-name">Band Name:</label>
<input id="band-name" type="text" maxlength="1024" value="" class="w80"><br />
</div>
</td>
<td id="tdBandWebsite" valign="middle" width="33%">
<div class="field">
<label for="band-website">Web Site:</label>
<input id="band-website" type="text" maxlength="4000" value="" class="w80">
</div>
</td>
</tr>
<tr>
<td id="tdBandCountry" valign="middle">
<div class="field">
<label for="band-country">Country:</label>
<select id="band-country" class="w80">
</select>
</div>
</td>
<td id="tdBandRegion" valign="middle">
<div class="field">
<label for="band-region">State/Region:</label>
<select id="band-region" class="w80">
</select>
</div>
</td>
<td id="tdBandCity" valign="middle">
<div class="field">
<label for="band-city">City:</label>
<select id="band-city" class="w80">
</select>
</div>
</td>
</tr>
<tr>
<td id="tdBandGenres" valign="top">
<div class="field">
<label for="band-genres">Genres:</label>
<div class="band-setup-genres w90">
<table id="band-genres" width="100%" cellpadding="10" cellspacing="6"></table>
</div>
</div>
</td>
<td id="tdBandBiography" valign="top" colspan="2">
<div class="field">
<label for="band-biography">Description / Bio:</label>
<textarea id="band-biography" class="band-setup-bio w90"></textarea>
</div>
</td>
</tr>
</table>
<br clear="all" />
<div class="right">
<a id="btn-band-setup-cancel" class="button-grey">CANCEL</a>&nbsp;&nbsp;
<a id="btn-band-setup-next" class="button-orange">NEXT</a>
</div>
<div class="clearall"></div>
</div>
<div id="band-setup-step-2" class="content-wrapper" style="padding:10px 35px 10px 35px; display:none;">
<br/>
<h2><span class="band-setup-text-step2">Step 2: </span>Add Band Members</h2><br/>
<div id="band-setup-invite-musicians"></div>
<br/><br/>
If your bandmates are not on JamKazam yet, use any of the options below to invite them to join the service.<br/><br/>
<div class="left mr20">
<div class="left">
<a class="btn-email-invitation">
<%= image_tag("content/icon_gmail.png", :size => "24x24", :align => "absmiddle") %>
</a>
</div>
<div class="right mt5 ml5">E-mail</div>
</div>
<div class="left mr20">
<div class="left">
<a class="btn-facebook-invitation">
<%= image_tag("content/icon_facebook.png", :size => "24x24", :align => "absmiddle") %>
</a>
</div>
<div class="right mt5 ml5">Facebook</div>
</div>
<!-- <div class="left mr20">
<div class="left">
<a href="/client#/createSession" title="This feature is not yet available.">
<%= image_tag("content/icon_twitter.png", :size => "24x24", :align => "absmiddle") %>
</a>
</div>
<div class="right mt5 ml5">Twitter</div>
</div> -->
<div class="left left">
<div class="left">
<a class="btn-gmail-invitation">
<%= image_tag("content/icon_google.png", :size => "24x24", :align => "absmiddle") %>
</a>
</div>
<div class="right mt5 ml5">Google+</div>
</div>
<br clear="all" />
<div class="right">
<a id="btn-band-setup-back" class="button-grey">BACK</a>&nbsp;&nbsp;
<a id="btn-band-setup-save" class="button-orange">CREATE BAND</a>
</div>
<div class="clearall"></div>
</div>
</div>
</form>
</div>
</div>
</div>
<script type="text/template" id="template-band-setup-genres">
tr
td <input value=" id}" {checked} type="checkbox" />{description
</script>
<script type="text/template" id="template-band-invitation">
<div user-id="{userId}" class="invitation">{userName}
<a><%= image_tag "shared/icon_delete_sm.png", :size => "13x13" %></a>
</div>
</script>

View File

@ -0,0 +1,127 @@
#band-setup.screen.secondary[layout="screen" layout-id="band/setup" layout-arg="id"]
.content-head
.content-icon
= image_tag "content/icon_bands.png", :size => "19x19"
h1#band-setup-title
| set up band
= render "screen_navigation"
.content-body
.content-body-scroller
form#band-setup-form
div[style="display:block;"]
#band-setup-step-1.content-wrapper[style="padding:10px 35px 10px 35px;"]
br
table[width="100%" cellpadding="0" cellspacing="0"]
tr
td[valign="top" colspan="2"]
h2
span.band-setup-text-step1
| Step 1:
| General Information
td#tdBandPhoto[valign="middle" rowspan="2" width="33%"]
a.avatar-profile[href="#"]
= image_tag "shared/avatar_generic_band.png", {:id => "band-avatar", :align=>"absmiddle", :height => 88, :width => 88 }
br
br
a#band-change-photo.small.ml20[href="#"]
| Upload band photo.
br[clear="all"]
br
tr
td#tdBandName[valign="middle" width="33%"]
.field
label[for="band-name"]
| Band Name:
input#band-name.w80[type="text" maxlength="1024" value=""]
br
td#tdBandWebsite[valign="middle" width="33%"]
.field
label[for="band-website"]
| Web Site:
input#band-website.w80[type="text" maxlength="4000" value=""]
tr
td#tdBandCountry[valign="middle"]
.field
label[for="band-country"]
| Country:
select#band-country.w80
td#tdBandRegion[valign="middle"]
.field
label[for="band-region"]
| State/Region:
select#band-region.w80
td#tdBandCity[valign="middle"]
.field
label[for="band-city"]
| City:
select#band-city.w80
tr
td#tdBandGenres[valign="top"]
.field
label[for="band-genres"]
| Genres:
.band-setup-genres.w90
table#band-genres[width="100%" cellpadding="10" cellspacing="6"]
td#tdBandBiography[valign="top" colspan="2"]
.field
label[for="band-biography"]
| Description / Bio:
textarea#band-biography.band-setup-bio.w90
br[clear="all"]
.right
a#btn-band-setup-cancel.button-grey
| CANCEL
|   
a#btn-band-setup-next.button-orange
| NEXT
.clearall
#band-setup-step-2.content-wrapper[style="padding:10px 35px 10px 35px; display:none;"]
br
h2
span.band-setup-text-step2
| Step 2:
| Add Band Members
br
#band-setup-invite-musicians
br
br
| If your bandmates are not on JamKazam yet, use any of the options below to invite them to join the service.
br
br
.left.mr20
.left
a.btn-email-invitation
= image_tag("content/icon_gmail.png", :size => "24x24", :align => "absmiddle")
.right.mt5.ml5
| E-mail
.left.mr20
.left
a.btn-facebook-invitation
= image_tag("content/icon_facebook.png", :size => "24x24", :align => "absmiddle")
.right.mt5.ml5
| Facebook
.left.left
.left
a.btn-gmail-invitation
= image_tag("content/icon_google.png", :size => "24x24", :align => "absmiddle")
.right.mt5.ml5
| Google+
br[clear="all"]
.right
a#btn-band-setup-back.button-grey
| BACK
|   
a#btn-band-setup-save.button-orange
| CREATE BAND
.clearall
script#template-band-setup-genres[type="text/template"]
tr
td <input value=" id}" {checked} type="checkbox" />{description
script#template-band-invitation[type="text/template"]
.invitation user-id="{userId}"
| {userName}
a =image_tag "shared/icon_delete_sm.png", :size => "13x13"

View File

@ -131,7 +131,7 @@
<input type="checkbox" id="intellectual-property" class="intellectual-property" /> <input type="checkbox" id="intellectual-property" class="intellectual-property" />
</div> </div>
<div id="divTerms" class="terms ml25"> <div id="divTerms" class="terms ml25">
I agree that intellectual property ownership of any musical works created during this session shall be governed by the terms of the <a href="http://creativecommons.org/licenses/by-nc-sa/3.0/" target="_blank">Creative Commons CC BY-NC-SA license</a> in accordance with the <a rel="external" href="http://www.jamkazam.com/corp/terms" target="_blank">JamKazam Terms of Service</a>. I agree that intellectual property ownership of any musical works created during this session shall be governed by the terms of the <a href="http://creativecommons.org/licenses/by-nc-sa/3.0/" target="_blank">Creative Commons CC BY-NC-SA license</a> in accordance with the <a rel="external" href="https://www.jamkazam.com/corp/terms" target="_blank">JamKazam Terms of Service</a>.
</div> </div>
</div> </div>
<br clear="all"/> <br clear="all"/>

View File

@ -140,20 +140,20 @@
<ul class="device_type"> <ul class="device_type">
<li class="ftue-video-link first" <li class="ftue-video-link first"
external-link-win="http://www.youtube.com/watch?v=b1JrwGeUcOo" external-link-win="https://www.youtube.com/watch?v=b1JrwGeUcOo"
external-link-mac="http://www.youtube.com/watch?v=TRzb7OTlO-Q"> external-link-mac="https://www.youtube.com/watch?v=TRzb7OTlO-Q">
AUDIO DEVICE WITH PORTS FOR INSTRUMENT OR MIC INPUT JACKS<br/> AUDIO DEVICE WITH PORTS FOR INSTRUMENT OR MIC INPUT JACKS<br/>
<p><%= image_tag "content/audio_capture_ftue.png", {:width => 243, :height => 70} %></p> <p><%= image_tag "content/audio_capture_ftue.png", {:width => 243, :height => 70} %></p>
</li> </li>
<li class="ftue-video-link" <li class="ftue-video-link"
external-link-win="http://www.youtube.com/watch?v=IDrLa8TOXwQ" external-link-win="https://www.youtube.com/watch?v=IDrLa8TOXwQ"
external-link-mac="http://www.youtube.com/watch?v=vIs7ArrjMpE"> external-link-mac="https://www.youtube.com/watch?v=vIs7ArrjMpE">
USB MICROPHONE<br/> USB MICROPHONE<br/>
<p><%= image_tag "content/microphone_ftue.png", {:width => 70, :height => 113} %></p> <p><%= image_tag "content/microphone_ftue.png", {:width => 70, :height => 113} %></p>
</li> </li>
<li class="ftue-video-link" <li class="ftue-video-link"
external-link-win="http://www.youtube.com/watch?v=PCri4Xed4CA" external-link-win="https://www.youtube.com/watch?v=PCri4Xed4CA"
external-link-mac="http://www.youtube.com/watch?v=Gatmd_ja47U"> external-link-mac="https://www.youtube.com/watch?v=Gatmd_ja47U">
COMPUTER'S BUILT-IN MIC &amp; SPEAKERS/HEADPHONES<br/> COMPUTER'S BUILT-IN MIC &amp; SPEAKERS/HEADPHONES<br/>
<p><%= image_tag "content/computer_ftue.png", {:width => 118, :height => 105} %></p> <p><%= image_tag "content/computer_ftue.png", {:width => 118, :height => 105} %></p>
</li> </li>

View File

@ -1,83 +0,0 @@
#jamtrackScreen.screen.secondary.no-login-required layout='screen' layout-id='jamtrackBrowse'
.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
h2 shop for jamtracks
table.generaltable
thead
tr
th JAMTRACK
th TRACKS INCLUDED / PREVIEW
th.center SHOP
tbody.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'
tr.jamtrack-record jamtrack-id="{{data.jamtrack.id}}"
td.jamtrack-detail
.detail-label
| Title:
.detail-value
| {{data.jamtrack.name}}
.clearall.detail-label
| Original Artist:
.detail-value
| {{data.jamtrack.original_artist}}
.clearall.detail-label
| Genre:
.detail-value
| {{data.jamtrack.genres[0]}}
.clearall.detail-label.extra.hidden
| Writer(s):
.detail-value.extra.hidden
| {{data.jamtrack.songwriter}}
.clearall.detail-label.extra.hidden
| Publisher:
.detail-value.extra.hidden
| {{data.jamtrack.publisher}}
td.jamtrack-tracks
.detail-arrow
.jamtrack-detail-btn.orange
="{% if (data.expanded) { %}"
| hide tracks
a.details-arrow.arrow-up-orange
="{% } else { %}"
| preview all tracks
a.details-arrow.arrow-down-orange
="{% } %}"
="{% _.each(data.jamtrack.tracks, function(track) { %}"
.jamtrack-track.hidden jamtrack-track-id="{{track.id}}"
/ .instrument-desc
/ | {{track.instrument_desc}}
/.track-instrument
.jamtrack-preview
.clearall
="{% }); %}"
td.jamtrack-action
/ a.play-button href="#" data-jamtrack-id="{{data.jamtrack.id}}"
/ =image_tag "shared/play_button.png"
.jamtrack-price class="{{data.free_state}}"
| {{"$ " + data.jamtrack.price}}
.free-state.hidden
| (first one is FREE)
="{% 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. &nbsp;&nbsp;&nbsp;&nbsp;
a.license-us-why.orange href="#" why?
="{% }; %}"

View File

@ -0,0 +1,86 @@
#jamtrackScreen.screen.secondary.no-login-required layout='screen' layout-id='jamtrackBrowse'
.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
h2 shop for jamtracks
table.generaltable.jamtrack-table
thead
tr
th.jamtrack-detail JAMTRACK
th.jamtrack-tracks TRACKS INCLUDED / PREVIEW
th.jamtrack-action SHOP
tbody.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'
tr.jamtrack-record jamtrack-id="{{data.jamtrack.id}}"
td.jamtrack-detail
.jamtrack-name
| "{{data.jamtrack.name}}"
.jamtrack-original-artist
| by {{data.jamtrack.original_artist}}
br clear="all"
.clearall.detail-label.extra.hidden.song-writer
| Songwriters:
.detail-value.extra.hidden
| {{data.jamtrack.songwriter}}
.clearall.detail-label.extra.hidden
| Publishers:
.detail-value.extra.hidden
| {{data.jamtrack.publisher}}
.clearall.detail-label.extra.hidden
| Genre:
.detail-value.extra.hidden
| {{data.jamtrack.genres[0]}}
.clearall.detail-label.extra.hidden
| Version:
.detail-value.extra.hidden
| {{data.jamtrack.recording_type}}
td.jamtrack-tracks
.detail-arrow
.jamtrack-detail-btn
="{% if (data.expanded) { %}"
| hide tracks
a.details-arrow.arrow-up
="{% } else { %}"
| show all tracks
a.details-arrow.arrow-down
="{% } %}"
="{% _.each(data.jamtrack.tracks, function(track) { %}"
.jamtrack-track.hidden jamtrack-track-id="{{track.id}}"
/ .instrument-desc
/ | {{track.instrument_desc}}
/.track-instrument
.jamtrack-preview
.clearall
="{% }); %}"
td.jamtrack-action
.jamtrack-action-container
.jamtrack-actions
/ a.play-button href="#" data-jamtrack-id="{{data.jamtrack.id}}"
/ =image_tag "shared/play_button.png"
.jamtrack-price class="{{data.free_state}}"
| {{"$ " + data.jamtrack.price}}
="{% if (data.is_free) { %}"
a.jamtrack-add-cart.button-orange.is_free href="#" data-jamtrack-id="{{data.jamtrack.id}}" GET IT FREE!
="{% } else 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. &nbsp;&nbsp;&nbsp;&nbsp;
a.license-us-why.orange href="#" why?
="{% }; %}"

View File

@ -163,11 +163,11 @@ div layout="screen" layout-id="order" id="orderScreen" class="screen secondary"
.mt5 .mt5
span By placing your order, you agree to JamKazam's span By placing your order, you agree to JamKazam's
' '
a href="http://www.jamkazam.com/corp/terms" terms of service a href="/corp/terms" terms of service
' '
span and span and
' '
a href="http://www.jamkazam.com/corp/returns" returns policy a href="/corp/returns" returns policy
span . span .
.right.mt10 .right.mt10
a href="#" class="button-grey" HELP a href="#" class="button-grey" HELP
@ -279,11 +279,11 @@ script type='text/template' id='template-order-content'
div style="text-align: left;" div style="text-align: left;"
span By placing your order, you agree to JamKazam's span By placing your order, you agree to JamKazam's
' '
a href="http://www.jamkazam.com/corp/terms" terms of service a href="/corp/terms" terms of service
' '
span and span and
' '
a href="http://www.jamkazam.com/corp/returns" returns policy a href="/corp/returns" returns policy
span . span .
script type='text/template' id='template-purchased-jam-track' script type='text/template' id='template-purchased-jam-track'

View File

@ -0,0 +1,45 @@
div layout="screen" layout-id="redeemComplete" id="redeemCompleteScreen" 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
.checkout-complete-wrapper
.no-purchases-prompt.hidden
| You have not made any purchases recently. Why not go go&nbsp;
a href="/client#/jamtrackBrowse" browse our collection of JamTracks
| ?
.thanks-panel
.jam-tracks-in-browser.hidden
p Thank you for joining our community, and congratulations on getting your first JamTrack!
p.why-download
| JamTracks are full multi-track recordings with lots of special features, so they are not&nbsp;
| just standard audio files. To play with your JamTrack, you'll need to download and install&nbsp;
| our free Mac or Windows app. This is the last step in the process, and you'll be ready to play.&nbsp;
| This free app also lets you play online in real time with other musicians over the Internet at no cost!
a.download-jamkazam-wrapper href="/downloads" rel="external"
.download-jamkazam
| Click Here to Get the Free JamKazam Application
.jam-tracks-in-client.hidden
h2 Congratulations on getting your JamTrack!
.thanks-detail.purchased-jam-track
h2.purchased-jam-track-header status="in-progress"
span.in-progress-msg Downloading Your JamTrack
span.done-msg All purchased JamTracks have been downloaded successfully! You can now play them in a session.
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
.clearall.hidden
.action-bar
.right
a.button-orange.checkout-done href="/client#/home" DONE
.clearall

View File

@ -0,0 +1,67 @@
div layout="screen" layout-id="redeemSignup" id="redeemSignupScreen" 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
.redeem-signup
.content-holder
.already-signed-in
h3 YOU ARE ALREADY LOGGED IN
p.carry-on-prompt
| You can go back to browsing.
.actions
a.btnNext.button-orange href="/client#/jamtrackBrowse" BROWSE JAMTRACKS
.not-signed-in.prompt
| To get your free&nbsp;
span.jamtrack-name
| &nbsp;JamTrack, join thousands of musicians in the JamKazam community by registering for your free account.
.left-side
.left-side-content
= link_to image_tag("content/button_facebook_signup.png", {:width => 249, :height => 46}), '/auth/facebook', class: "signup-facebook"
br clear="all"
.field.terms_of_service
input type="checkbox"
label
| I have read and agree to the JamKazam
br
= link_to "terms of service", corp_terms_path, rel: "external"
|.
br clear="all"
.right-side
h3 OR SIGN UP USING YOUR EMAIL
.signup-form
form.signup-form
.input-elements
.field
label.inline First Name:
input name='first_name' autofocus="true" type="text"
.field
label.inline Last Name:
input name='last_name' type="text"
.field
label.inline Email:
input name='email' type="text"
.field
label.inline Password:
input name='password' autofocus="true" type="password"
.field.terms_of_service
input type="checkbox"
label
| I have read and agree to the JamKazam &nbsp;
= link_to "terms of service", corp_terms_path, rel: "external"
|.
.field.recaptcha style="display:none"
- if Rails.application.config.recaptcha_enable
#recaptcha_select name="recaptcha_response_field" class="g-recaptcha" data-sitekey=Rails.application.config.recaptcha_public_key
.actions
input.button-orange.signup-submit type="submit" value="SIGNUP"
= link_to "Already have a JamKazam account? Sign in", "#", class: 'signin'
br clear='all'

View File

@ -37,13 +37,15 @@
<%= render "band_setup_photo" %> <%= render "band_setup_photo" %>
<%= render "users/feed_music_session_ajax" %> <%= render "users/feed_music_session_ajax" %>
<%= render "users/feed_recording_ajax" %> <%= render "users/feed_recording_ajax" %>
<%= render "jamtrack" %> <%= render "jamtrack_browse" %>
<%= render "jamtrack_landing" %> <%= render "jamtrack_landing" %>
<%= render "shopping_cart" %> <%= render "shopping_cart" %>
<%= render "checkout_signin" %> <%= render "checkout_signin" %>
<%= render "checkout_payment" %> <%= render "checkout_payment" %>
<%= render "checkout_order" %> <%= render "checkout_order" %>
<%= render "checkout_complete" %> <%= render "checkout_complete" %>
<%= render "redeem_signup" %>
<%= render "redeem_complete" %>
<%= render "order" %> <%= render "order" %>
<%= render "feed" %> <%= render "feed" %>
<%= render "bands" %> <%= render "bands" %>
@ -114,12 +116,14 @@
JK.currentUserName = '<%= current_user.name %>'; JK.currentUserName = '<%= current_user.name %>';
JK.currentUserMusician = '<%= current_user.musician %>'; JK.currentUserMusician = '<%= current_user.musician %>';
JK.currentUserAdmin = <%= current_user.admin %>; JK.currentUserAdmin = <%= current_user.admin %>;
JK.currentUserFreeJamTrack = <%= APP_CONFIG.one_free_jamtrack_per_user && current_user.has_redeemable_jamtrack %>
<% else %> <% else %>
JK.currentUserId = null; JK.currentUserId = null;
JK.currentUserAvatarUrl = null; JK.currentUserAvatarUrl = null;
JK.currentUserName = null; JK.currentUserName = null;
JK.currentUserMusician = null; JK.currentUserMusician = null;
JK.currentUserAdmin = false; JK.currentUserAdmin = false;
JK.currentUserFreeJamTrack = <%= APP_CONFIG.one_free_jamtrack_per_user && anonymous_user.nil? ? false : anonymous_user.has_redeemable_jamtrack%>
<% end %> <% end %>
@ -296,6 +300,13 @@
// var findMusicianScreen = new JK.FindMusicianScreen(JK.app); // var findMusicianScreen = new JK.FindMusicianScreen(JK.app);
//findMusicianScreen.initialize(JK.TextMessageDialogInstance); //findMusicianScreen.initialize(JK.TextMessageDialogInstance);
var redeemSignUpScreen = new JK.RedeemSignUpScreen(JK.app);
redeemSignUpScreen.initialize();
var redeemCompleteScreen = new JK.RedeemCompleteScreen(JK.app);
redeemCompleteScreen.initialize();
var findBandScreen = new JK.FindBandScreen(JK.app); var findBandScreen = new JK.FindBandScreen(JK.app);
findBandScreen.initialize(); findBandScreen.initialize();
@ -326,6 +337,10 @@
var singlePlayerProfileGuardDialog = new JK.SinglePlayerProfileGuardDialog(JK.app); var singlePlayerProfileGuardDialog = new JK.SinglePlayerProfileGuardDialog(JK.app);
singlePlayerProfileGuardDialog.initialize(); singlePlayerProfileGuardDialog.initialize();
var signinDialog = new JK.SigninDialog(JK.app);
signinDialog.initialize();
JK.SigninPage(); // initialize signin helper
// do a client update early check upon initialization // do a client update early check upon initialization
JK.ClientUpdateInstance.check() JK.ClientUpdateInstance.check()

View File

@ -14,18 +14,18 @@
%ul.device_type %ul.device_type
%li.ftue-video-link.first{'external-link-win'=>"http://www.youtube.com/watch?v=b1JrwGeUcOo", 'external-link-mac'=>"http://www.youtube.com/watch?v=TRzb7OTlO-Q"} %li.ftue-video-link.first{'external-link-win'=>"https://www.youtube.com/watch?v=b1JrwGeUcOo", 'external-link-mac'=>"https://www.youtube.com/watch?v=TRzb7OTlO-Q"}
AUDIO DEVICE WITH PORTS FOR INSTRUMENT OR MIC INPUT JACKS AUDIO DEVICE WITH PORTS FOR INSTRUMENT OR MIC INPUT JACKS
%br %br
%p %p
= image_tag "content/audio_capture_ftue.png", {:width => 243, :height => 70} = image_tag "content/audio_capture_ftue.png", {:width => 243, :height => 70}
%li.ftue-video-link{'external-link-win'=>"http://www.youtube.com/watch?v=IDrLa8TOXwQ",'external-link-mac'=>"http://www.youtube.com/watch?v=vIs7ArrjMpE"} %li.ftue-video-link{'external-link-win'=>"https://www.youtube.com/watch?v=IDrLa8TOXwQ",'external-link-mac'=>"https://www.youtube.com/watch?v=vIs7ArrjMpE"}
USB MICROPHONE USB MICROPHONE
%br %br
%p %p
= image_tag "content/microphone_ftue.png", {:width => 70, :height => 113} = image_tag "content/microphone_ftue.png", {:width => 70, :height => 113}
%li.ftue-video-link{'external-link-win'=>"http://www.youtube.com/watch?v=PCri4Xed4CA",'external-link-mac'=>"http://www.youtube.com/watch?v=Gatmd_ja47U"} %li.ftue-video-link{'external-link-win'=>"https://www.youtube.com/watch?v=PCri4Xed4CA",'external-link-mac'=>"https://www.youtube.com/watch?v=Gatmd_ja47U"}
COMPUTER'S BUILT-IN MIC &amp; SPEAKERS/HEADPHONES COMPUTER'S BUILT-IN MIC &amp; SPEAKERS/HEADPHONES
%br %br
%p %p

View File

@ -18,20 +18,20 @@
<h3>Classical: Pachelbels “Canon in D”</h3> <h3>Classical: Pachelbels “Canon in D”</h3>
<ul> <ul>
<li><a href="http://www.jamkazam.com/recordings/b3071f33-4514-4f05-8b36-8db8edeab23e" target="_blank">Real-Time Streamed Recording</a></li> <li><a href="https://www.jamkazam.com/recordings/b3071f33-4514-4f05-8b36-8db8edeab23e" target="_blank">Real-Time Streamed Recording</a></li>
<li><a href="http://www.jamkazam.com/recordings/70e9a449-b142-436c-9fb0-cec133a0b429" target="_blank">Mastered Recording</a></li> <li><a href="https://www.jamkazam.com/recordings/70e9a449-b142-436c-9fb0-cec133a0b429" target="_blank">Mastered Recording</a></li>
</ul> </ul>
<h3>Rock: Poisons “Nothin But a Good Time”</h3> <h3>Rock: Poisons “Nothin But a Good Time”</h3>
<ul> <ul>
<li><a href="http://www.jamkazam.com/recordings/c9e5aec1-662e-4e75-89a9-546f6399c342" target="_blank">Real-Time Streamed Recording</a></li> <li><a href="https://www.jamkazam.com/recordings/c9e5aec1-662e-4e75-89a9-546f6399c342" target="_blank">Real-Time Streamed Recording</a></li>
<li><a href="http://www.jamkazam.com/recordings/05a9ae58-f183-43ae-b57d-54ccf932d15c" target="_blank">Mastered Recording</a></li> <li><a href="https://www.jamkazam.com/recordings/05a9ae58-f183-43ae-b57d-54ccf932d15c" target="_blank">Mastered Recording</a></li>
</ul> </ul>
<h3>Jazz: Jimmy Van Heusens and Johnny Burkes “But Beautiful”</h3> <h3>Jazz: Jimmy Van Heusens and Johnny Burkes “But Beautiful”</h3>
<ul> <ul>
<li><a href="http://www.jamkazam.com/recordings/eaf88dd0-4b66-4e2b-a856-7e312c392cff" target="_blank">Real-Time Streamed Recording</a></li> <li><a href="https://www.jamkazam.com/recordings/eaf88dd0-4b66-4e2b-a856-7e312c392cff" target="_blank">Real-Time Streamed Recording</a></li>
<li><a href="http://www.jamkazam.com/recordings/df68065a-8159-4315-957f-9530549dbbac" target="_blank">Mastered Recording</a></li> <li><a href="https://www.jamkazam.com/recordings/df68065a-8159-4315-957f-9530549dbbac" target="_blank">Mastered Recording</a></li>
</ul> </ul>

View File

@ -69,7 +69,7 @@
| There is a lot you can do with JamKazam, and more great features available every week. Check the | There is a lot you can do with JamKazam, and more great features available every week. Check the
following link for a list of videos and other resources you can use to take advantage of everything thats following link for a list of videos and other resources you can use to take advantage of everything thats
available: available:
a rel="external" purpose="youtube-tutorials" href="http://www.youtube.com/channel/UC38nc9MMZgExJAd7ca3rkUA" JamKazam Tutorials & Resources a rel="external" purpose="youtube-tutorials" href="https://www.youtube.com/channel/UC38nc9MMZgExJAd7ca3rkUA" JamKazam Tutorials & Resources
br clear="both" br clear="both"
.row.dialog-buttons .row.dialog-buttons
.buttons .buttons

View File

@ -10,7 +10,7 @@
<!-- inner wrapper --> <!-- inner wrapper -->
<div class="overlay-video-inner"> <div class="overlay-video-inner">
<iframe id="video-dialog-iframe" width="550" height="309" frameborder="0" allowfullscreen></iframe> <iframe id="video-dialog-iframe" width="550" height="309" frameborder="0" allowfullscreen="allowfullscreen"></iframe>
</div> </div>
<!-- end inner --> <!-- end inner -->

View File

@ -46,10 +46,10 @@
<br><br> <br><br>
<div class="left f20"><br> <div class="left f20"><br>
<a purpose="youtube-tutorials" rel="external" href="http://www.youtube.com/channel/UC38nc9MMZgExJAd7ca3rkUA" class="orange">See a List of<br> <a purpose="youtube-tutorials" rel="external" href="https://www.youtube.com/channel/UC38nc9MMZgExJAd7ca3rkUA" class="orange">See a List of<br>
Videos »</a></div> Videos »</a></div>
<div class="right mr20"> <div class="right mr20">
<a purpose="youtube-tutorials" rel="external" href="http://www.youtube.com/channel/UC38nc9MMZgExJAd7ca3rkUA"> <a purpose="youtube-tutorials" rel="external" href="https://www.youtube.com/channel/UC38nc9MMZgExJAd7ca3rkUA">
<%= image_tag "content/ftue_whatsnext_videos.png", {:height => 90, :width => 152 } %> <%= image_tag "content/ftue_whatsnext_videos.png", {:height => 90, :width => 152 } %>
</a> </a>
</div> </div>

View File

@ -39,7 +39,7 @@
%div{align: 'center'} LEARN ABOUT JAMKAZAM</div> %div{align: 'center'} LEARN ABOUT JAMKAZAM</div>
%br %br
= image_tag 'web/carousel_musicians.jpg', width:350, alt:'JamKazam Overview', class: 'video-slide', = image_tag 'web/carousel_musicians.jpg', width:350, alt:'JamKazam Overview', class: 'video-slide',
:'data-video-header' => 'JamKazam Overview', :'data-video-url' => 'http://www.youtube.com/embed/ylYcvTY9CVo?autoplay=1' :'data-video-header' => 'JamKazam Overview', :'data-video-url' => 'https://www.youtube.com/embed/ylYcvTY9CVo?autoplay=1'
%br{clear:'all'} %br{clear:'all'}

View File

@ -1,42 +1,26 @@
- provide(:page_name, 'landing_page full landing_jamtrack individual_jamtrack') - provide(:page_name, 'landing_page full landing_jamtrack individual_jamtrack')
- provide(:description, @jam_track.nil? ? nil : "Preview multi-track JamTrack recording: #{@jam_track.name} by #{@jam_track.original_artist}. Way better than a backing track.") - provide(:description, @jam_track.nil? ? nil : "Preview multi-track JamTrack recording: #{@jam_track.name} by #{@jam_track.original_artist}. Way better than a backing track.")
.two_by_two .one_by_two
.row .row
.column .column
h1.hidden.individualized h1.hidden.individualized
| Check Out Our&nbsp; | Preview&nbsp;
strong.jamtrack_name strong.jamtrack_name
| &nbsp;JamTrack br
| JamTrack by&nbsp;
span.jamtrack_band
h1.hidden.generic h1.hidden.generic
| We Have 100+ Amazing JamTracks, Check One Out! | Preview One of Our JamTracks
p Click the play buttons below to hear the master mix and each fully isolated track. All are included in each single JamTrack. p.prompt Click play buttons to preview tracks. Every JamTrack includes fully isolated tracks for each part of the song!
.previews .previews
a.browse-jamtracks.hidden
.column .column
h1 See What You Can Do With JamTracks h1 See What You Can Do With JamTracks
.video-wrapper .video-wrapper
.video-container .video-container
iframe src="//www.youtube.com/embed/askHvcCoNfw" frameborder="0" allowfullscreen iframe src="//www.youtube.com/embed/askHvcCoNfw" frameborder="0" allowfullscreen="allowfullscreen"
br clear="all"
.row
.column
h1
| Get Your First JamTrack Free Now!
p Click the GET A JAMTRACK FREE button below. Browse to find the one you want. Click Add to cart, and we'll apply a credit during checkout to make this first one free! We're confident you'll be back for more.
.browse-jamtracks-wrapper
a.white-bordered-button href="/client#/jamtrackBrowse" GET A JAMTRACK FREE!
.column
h1 Why Are JamTracks Different & Better?
p
| JamTracks are the best way to play with your favorite music.&nbsp;
| Unlike traditional backing tracks, JamTracks are complete multitrack recordings,&nbsp;
| with fully isolated tracks for each part. Used with the free JamKazam app/service, you can:
ul.jamtrack-reasons
li Solo just the individual track you want to play to hear and learn it
li Mute just the track 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 And even go online to play JamTracks with others in real time!
br clear="all" br clear="all"
br clear="all" br clear="all"

View File

@ -1,42 +1,22 @@
- provide(:page_name, 'landing_page full landing_jamtrack individual_jamtrack_band') - provide(:page_name, 'landing_page full landing_jamtrack individual_jamtrack_band')
- provide(:description, @jam_track.nil? ? nil : "Preview multi-track JamTrack recording: #{@jam_track.name} by #{@jam_track.original_artist}. Way better than a backing track.") - provide(:description, @jam_track.nil? ? nil : "Preview multi-track JamTrack recording: #{@jam_track.name} by #{@jam_track.original_artist}. Way better than a backing track.")
.two_by_two .one_by_two
.row .row
.column .column
h1 h1
| We Have&nbsp; | Preview a&nbsp;
span.jamtrack_band_info | JamTrack by&nbsp;
| &nbsp; span.jamtrack_band
span.jamtrack_noun JamTracks
span.check-it-out , Check One Out! p.prompt Click play buttons to preview tracks. Every JamTrack includes fully isolated tracks for each part of the song!
p Click the play buttons below to hear the master mix and each fully isolated track. All are included in each single JamTrack.
.previews .previews
a.browse-jamtracks.hidden
.column .column
h1 See What You Can Do With JamTracks h1 See What You Can Do With JamTracks
.video-wrapper .video-wrapper
.video-container .video-container
iframe src="//www.youtube.com/embed/askHvcCoNfw" frameborder="0" allowfullscreen iframe src="//www.youtube.com/embed/askHvcCoNfw" frameborder="0" allowfullscreen="allowfullscreen"
br clear="all"
.row
.column
h1
| Get Your First JamTrack Free Now!
p Click the GET A JAMTRACK FREE button below. Browse to find the one you want. Click Add to cart, and we'll apply a credit during checkout to make this first one free! We're confident you'll be back for more.
.browse-jamtracks-wrapper
a.white-bordered-button href="/client#/jamtrackBrowse" GET A JAMTRACK FREE!
.column
h1 Why Are JamTracks Different & Better?
p
| JamTracks are the best way to play with your favorite music.&nbsp;
| Unlike traditional backing tracks, JamTracks are complete multitrack recordings,&nbsp;
| with fully isolated tracks for each part. Used with the free JamKazam app/service, you can:
ul.jamtrack-reasons
li Solo just the individual track you want to play to hear and learn it
li Mute just the track 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 And even go online to play JamTracks with others in real time!
br clear="all" br clear="all"
br clear="all" br clear="all"

View File

@ -18,7 +18,7 @@
h1 See What You Can Do With The JamBlaster h1 See What You Can Do With The JamBlaster
.video-wrapper .video-wrapper
.video-container .video-container
iframe src="//www.youtube.com/embed/YHZQHfKDdMc" frameborder="0" allowfullscreen iframe src="//www.youtube.com/embed/YHZQHfKDdMc" frameborder="0" allowfullscreen="allowfullscreen"
br clear="all" br clear="all"
.row .row
.column .column
@ -69,7 +69,7 @@ javascript:
confirmOrder(data.app); confirmOrder(data.app);
}) })
} }
else if(window.JK.signup && window.JK.signup.want_jamblaster) { else if(window.JK.signupData && window.JK.signupData.want_jamblaster) {
// if the user has come here as a result of a signup attempt, and if they indicated that they want the jamblaster, tell em it's done // if the user has come here as a result of a signup attempt, and if they indicated that they want the jamblaster, tell em it's done
promptConfirmed(data.app) promptConfirmed(data.app)
} }

View File

@ -13,7 +13,7 @@
h1 See What You Can With JamTracks h1 See What You Can With JamTracks
.video-wrapper .video-wrapper
.video-container .video-container
iframe src="//www.youtube.com/embed/askHvcCoNfw" frameborder="0" allowfullscreen iframe src="//www.youtube.com/embed/askHvcCoNfw" frameborder="0" allowfullscreen="allowfullscreen"
br clear="all" br clear="all"
.row .row
.column .column

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