Mute or unmute any tracks you like. You can also use the controls below to adjust the tempo or pitch of the JamTrack. Then give your custom mix a name, and click the Create Mix button.
+
Mute or unmute any tracks you like. You can also use the controls below to adjust the tempo or pitch of the
+ JamTrack. Then give your custom mix a name, and click the Create Mix button.
`
+ copyProfileLink: (e) ->
+
+ e.preventDefault()
+
+ @app.layout.notify({
+ title: 'Teacher Profile Link Copied',
+ text: "Your clipboard now has a link to this teacher that you can share with anyone."
+ })
+
+
selectionMade: (selection, e) ->
e.preventDefault()
diff --git a/web/app/assets/javascripts/react-components/actions/MixerActions.js.coffee b/web/app/assets/javascripts/react-components/actions/MixerActions.js.coffee
index 68c71dfba..cbb7916cc 100644
--- a/web/app/assets/javascripts/react-components/actions/MixerActions.js.coffee
+++ b/web/app/assets/javascripts/react-components/actions/MixerActions.js.coffee
@@ -14,4 +14,5 @@ context = window
metronomeChanged: {}
deadUserRemove: {}
missingPeerMixer: {}
+ clientsWithAudio: {}
})
diff --git a/web/app/assets/javascripts/react-components/actions/RecordingActions.js.coffee b/web/app/assets/javascripts/react-components/actions/RecordingActions.js.coffee
index f6b111ac9..ad49fc0ca 100644
--- a/web/app/assets/javascripts/react-components/actions/RecordingActions.js.coffee
+++ b/web/app/assets/javascripts/react-components/actions/RecordingActions.js.coffee
@@ -11,4 +11,5 @@ context = window
abortedRecording: {}
openRecordingControls: {}
recordingControlsClosed: {}
+ mixTransferred: {}
})
\ No newline at end of file
diff --git a/web/app/assets/javascripts/react-components/helpers/MixerHelper.js.coffee b/web/app/assets/javascripts/react-components/helpers/MixerHelper.js.coffee
index aa28215d6..a475caad5 100644
--- a/web/app/assets/javascripts/react-components/helpers/MixerHelper.js.coffee
+++ b/web/app/assets/javascripts/react-components/helpers/MixerHelper.js.coffee
@@ -8,7 +8,7 @@ MIX_MODES = context.JK.MIX_MODES;
@MixerHelper = class MixerHelper
- constructor: (@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @mixMode) ->
+ constructor: (@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @clientsWithAudioOverride, @mixMode) ->
@mixMode = MIX_MODES.PERSONAL # TODO - remove mixMode from MixerHelper? Or at least stop using it in most functions
@app = @session.app
@mixersByResourceId = {}
@@ -622,13 +622,12 @@ MIX_MODES = context.JK.MIX_MODES;
return mixerPair.personal
- findMixerForTrack: (client_id, track, myTrack, mode = MIX_MODES.PERSONAL) ->
+ findMixerForTrack: (client_id, track, myTrack, mode) ->
mixer = null # what is the best mixer for this track/client ID?
oppositeMixer = null # what is the corresponding mixer in the opposite mode?
vuMixer = null
muteMixer = null
-
if myTrack
# when it's your track, look it up by the backend resource ID
mixer = @getMixerByTrackId(track.client_track_id, mode)
@@ -674,7 +673,7 @@ MIX_MODES = context.JK.MIX_MODES;
oppositeMixer = oppositeMixers[ChannelGroupIds.UserMusicInputGroup][0]
if !oppositeMixer
- logger.warn("unable to find UserMusicInputGroup corresponding to PeerAudioInputMusicGroup mixer", mixer, @personalMixers )
+ logger.warn("unable to find UserMusicInputGroup corresponding to PeerAudioInputMusicGroup mixer", mixer, @personalMixers)
when MIX_MODES.PERSONAL
mixers = @groupedMixersForClientId(client_id, [ ChannelGroupIds.UserMusicInputGroup], {}, MIX_MODES.PERSONAL)
@@ -693,6 +692,8 @@ MIX_MODES = context.JK.MIX_MODES;
logger.error("personaol: found remote mixer that was not of groupID: PeerAudioInputMusicGroup", client_id, track.client_track_id, mixer)
#vuMixer = oppositeMixer; # for personal mode, use the PeerAudioInputMusicGroup's VUs
+ else
+ logger.error("no UserMusicInputGroup for client_id #{client_id} in PERSONAL mode", mixers)
{
mixer: mixer,
@@ -729,26 +730,43 @@ MIX_MODES = context.JK.MIX_MODES;
originalVolume
- faderChanged: (data, mixers, gainType) ->
+ faderChanged: (data, mixers, gainType, controlGroup) ->
mixers = [mixers] unless $.isArray(mixers)
originalVolume = @getOriginalVolume(mixers, gainType)
- for mixer in mixers
- broadcast = !(data.dragging) # If fader is still dragging, don't broadcast
- mixer = @fillTrackVolumeObject(mixer.id, mixer.mode, broadcast)
+ if controlGroup?
+ mixers = [mixers[0]]
- relative = gainType == 'music' && (mixer.name == CategoryGroupIds.UserMedia || mixer.name == CategoryGroupIds.MediaTrack)
+ for mixer in mixers
+ broadcast = !(data.dragging) # If fader is still dragging, don't broadcast
+ mixer = @fillTrackVolumeObject(mixer.id, mixer.mode, broadcast)
- @setMixerVolume(mixer, data.percentage, relative, originalVolume)
+ relative = gainType == 'music' && (mixer.name == CategoryGroupIds.UserMedia || mixer.name == CategoryGroupIds.MediaTrack)
- # keep state of mixer in sync with backend
- mixer = @getMixer(mixer.id, mixer.mode)
- mixer.volume_left = context.trackVolumeObject.volL
+ @setMixerVolume(mixer, data.percentage, relative, originalVolume, controlGroup)
- #if groupId == ChannelGroupIds.UserMusicInputGroup
- # # there may be other mixers with this same ID in the case of a Peer Music Stream, so update them as well
- # context.JK.FaderHelpers.setFaderValue(mixerId, data.percentage)
+ # keep state of mixer in sync with backend
+ mixer = @getMixer(mixer.id, mixer.mode)
+ mixer.volume_left = context.trackVolumeObject.volL
+
+ else
+
+ for mixer in mixers
+ broadcast = !(data.dragging) # If fader is still dragging, don't broadcast
+ mixer = @fillTrackVolumeObject(mixer.id, mixer.mode, broadcast)
+
+ relative = gainType == 'music' && (mixer.name == CategoryGroupIds.UserMedia || mixer.name == CategoryGroupIds.MediaTrack)
+
+ @setMixerVolume(mixer, data.percentage, relative, originalVolume)
+
+ # keep state of mixer in sync with backend
+ mixer = @getMixer(mixer.id, mixer.mode)
+ mixer.volume_left = context.trackVolumeObject.volL
+
+ #if groupId == ChannelGroupIds.UserMusicInputGroup
+ # # there may be other mixers with this same ID in the case of a Peer Music Stream, so update them as well
+ # context.JK.FaderHelpers.setFaderValue(mixerId, data.percentage)
initGain: (mixer) ->
if $.isArray(mixer)
@@ -791,7 +809,7 @@ MIX_MODES = context.JK.MIX_MODES;
mixer = @getMixer(mixer.id, mixer.mode)
mixer.loop = context.trackVolumeObject.loop
- setMixerVolume: (mixer, volumePercent, relative, originalVolume) ->
+ setMixerVolume: (mixer, volumePercent, relative, originalVolume, controlGroup) ->
###
// The context.trackVolumeObject has been filled with the mixer values
// that go with mixerId, and the range of that mixer
@@ -821,7 +839,15 @@ MIX_MODES = context.JK.MIX_MODES;
else
context.trackVolumeObject.volL = newVolume
context.trackVolumeObject.volR = newVolume
- context.jamClient.SessionSetControlState(mixer.id, mixer.mode);
+ if controlGroup?
+
+ if mixer.mode == MIX_MODES.PERSONAL
+ controlGroupsArg = 0
+ else
+ controlGroupsArg = 1
+ context.jamClient.setSessionMixerCategoryPlayoutState(controlGroup == 'music', controlGroupsArg);
+ else
+ context.jamClient.SessionSetControlState(mixer.id, mixer.mode);
percentFromMixerValue: (min, max, value) ->
try
diff --git a/web/app/assets/javascripts/react-components/landing/JamClassAffiliateLandingBottomPage.js.jsx.coffee b/web/app/assets/javascripts/react-components/landing/JamClassAffiliateLandingBottomPage.js.jsx.coffee
index 204353571..5ae864747 100644
--- a/web/app/assets/javascripts/react-components/landing/JamClassAffiliateLandingBottomPage.js.jsx.coffee
+++ b/web/app/assets/javascripts/react-components/landing/JamClassAffiliateLandingBottomPage.js.jsx.coffee
@@ -98,7 +98,7 @@ rest = context.JK.Rest()
And more…
- JamTracks sell for $1.99 each. Musicians love to play with these, and typically buy a few at a
+ JamTracks sell for $1.99 each ($4.99 to include ability to download). Musicians love to play with these, and typically buy a few at a
time. Imagine that you are selling a set of guitar strings to an electric guitar player. As a
diff --git a/web/app/assets/javascripts/react-components/landing/JamTrackCta.js.jsx.coffee b/web/app/assets/javascripts/react-components/landing/JamTrackCta.js.jsx.coffee
index 490751fd5..1d2cdea72 100644
--- a/web/app/assets/javascripts/react-components/landing/JamTrackCta.js.jsx.coffee
+++ b/web/app/assets/javascripts/react-components/landing/JamTrackCta.js.jsx.coffee
@@ -72,7 +72,7 @@ rest = context.JK.Rest()
{img}
- $1.99 value
+ #{this.props.jam_track.download_price} value
diff --git a/web/app/assets/javascripts/react-components/landing/JamTrackLandingPage.js.jsx.coffee b/web/app/assets/javascripts/react-components/landing/JamTrackLandingPage.js.jsx.coffee
index b3a19c414..0ccbe995e 100644
--- a/web/app/assets/javascripts/react-components/landing/JamTrackLandingPage.js.jsx.coffee
+++ b/web/app/assets/javascripts/react-components/landing/JamTrackLandingPage.js.jsx.coffee
@@ -35,10 +35,10 @@ rest = context.JK.Rest()
if loggedIn
loggedInCtaButton = ``
if !@isFree()
- loggedInPriceAdvisory = `
${this.props.jam_track.price}
`
+ loggedInPriceAdvisory = `
${this.props.jam_track.download_price}
`
else
if !@isFree()
- loggedOutPriceAdvisory = `
${this.props.jam_track.price}
`
+ loggedOutPriceAdvisory = `
${this.props.jam_track.download_price}
`
if this.state.loginErrors?
for key, value of this.state.loginErrors
diff --git a/web/app/assets/javascripts/react-components/landing/ProductJamBlasterBottomPage.js.jsx.coffee b/web/app/assets/javascripts/react-components/landing/ProductJamBlasterBottomPage.js.jsx.coffee
index 44fae616f..a56e46588 100644
--- a/web/app/assets/javascripts/react-components/landing/ProductJamBlasterBottomPage.js.jsx.coffee
+++ b/web/app/assets/javascripts/react-components/landing/ProductJamBlasterBottomPage.js.jsx.coffee
@@ -108,7 +108,7 @@ context = window
-
Your first JamTrack is free, and after that JamTracks are just $1.99 each.
+
Your first JamTrack is free, and after that JamTracks are just $1.99/$4.99 each.
diff --git a/web/app/assets/javascripts/react-components/landing/RedeemGiftCardPage.js.jsx.coffee b/web/app/assets/javascripts/react-components/landing/RedeemGiftCardPage.js.jsx.coffee
index 57ca08d88..cfb102bc1 100644
--- a/web/app/assets/javascripts/react-components/landing/RedeemGiftCardPage.js.jsx.coffee
+++ b/web/app/assets/javascripts/react-components/landing/RedeemGiftCardPage.js.jsx.coffee
@@ -22,7 +22,7 @@ badCode = 'This is not a valid code. Please carefully re-enter the code and try
if errorText? && errorText.indexOf('already claimed') > -1
errorText = 'This card has already been claimed. If you believe this is in error, please email us at support@jamkazam.com to report this problem.'
-
+
buttonClassnames = classNames({'redeem-giftcard': true, 'button-orange': true, disabled: @state.processing || @state.done })
diff --git a/web/app/assets/javascripts/react-components/landing/SimpleJamClassPage.js.jsx.coffee b/web/app/assets/javascripts/react-components/landing/SimpleJamClassPage.js.jsx.coffee
new file mode 100644
index 000000000..5d37d2972
--- /dev/null
+++ b/web/app/assets/javascripts/react-components/landing/SimpleJamClassPage.js.jsx.coffee
@@ -0,0 +1,16 @@
+context = window
+rest = context.JK.Rest()
+
+@SimpleJamClassPage = React.createClass({
+
+ render: () ->
+
+ `
+
+
+
+
+
+
`
+
+})
\ No newline at end of file
diff --git a/web/app/assets/javascripts/react-components/landing/SimpleJamTracksPage.js.jsx.coffee b/web/app/assets/javascripts/react-components/landing/SimpleJamTracksPage.js.jsx.coffee
new file mode 100644
index 000000000..39a7ab05b
--- /dev/null
+++ b/web/app/assets/javascripts/react-components/landing/SimpleJamTracksPage.js.jsx.coffee
@@ -0,0 +1,16 @@
+context = window
+rest = context.JK.Rest()
+
+@SimpleJamTracksPage = React.createClass({
+
+ render: () ->
+
+ `
+
+
+
+
+
+
`
+
+})
\ No newline at end of file
diff --git a/web/app/assets/javascripts/react-components/mixins/SessionMyTracksMixin.js.coffee b/web/app/assets/javascripts/react-components/mixins/SessionMyTracksMixin.js.coffee
index a25950505..6a6430b33 100644
--- a/web/app/assets/javascripts/react-components/mixins/SessionMyTracksMixin.js.coffee
+++ b/web/app/assets/javascripts/react-components/mixins/SessionMyTracksMixin.js.coffee
@@ -28,6 +28,12 @@ MIDI_TRACK = context.JK.MIDI_TRACK
if session.inSession()
participant = session.getParticipant(@app.clientId)
+ connStatsClientId = @app.clientId
+
+ if participant.client_role == 'child' && participant.parent_client_id?
+ participant.parent = session.getParticipant(participant.parent_client_id)
+ connStatsClientId = participant.parent_client_id
+
if participant
photoUrl = context.JK.resolveAvatarUrl(participant.user.photo_url);
@@ -79,7 +85,7 @@ MIDI_TRACK = context.JK.MIDI_TRACK
associatedVst = vst
break
- tracks.push({track: track, mixerFinder: mixerFinder, mixers: mixerData, hasMixer:hasMixer, name: name, trackName: trackName, instrumentIcon: instrumentIcon, photoUrl: photoUrl, clientId: participant.client_id, associatedVst: associatedVst})
+ tracks.push({track: track, mixerFinder: mixerFinder, mixers: mixerData, hasMixer:hasMixer, name: name, trackName: trackName, instrumentIcon: instrumentIcon, photoUrl: photoUrl, clientId: participant.client_id, associatedVst: associatedVst, connStatsClientId: connStatsClientId})
else
logger.warn("SessionMyTracks: unable to find participant")
diff --git a/web/app/assets/javascripts/react-components/stores/CallbackStore.js.coffee b/web/app/assets/javascripts/react-components/stores/CallbackStore.js.coffee
index 65bb1d115..f5b81df87 100644
--- a/web/app/assets/javascripts/react-components/stores/CallbackStore.js.coffee
+++ b/web/app/assets/javascripts/react-components/stores/CallbackStore.js.coffee
@@ -1,6 +1,7 @@
$ = jQuery
context = window
logger = context.JK.logger
+RecordingActions = @RecordingActions
SessionActions = @SessionActions
JamBlasterActions = @JamBlasterActions
@@ -27,6 +28,9 @@ JamBlasterActions = @JamBlasterActions
JamBlasterActions.pairState(map)
else if map.cmd == 'jamblaster_tracks_updated'
JamBlasterActions.jamblasterTracksUpdated()
+ else if map.cmd == 'file_xfer_from_parent'
+ if map.filename && map.filename.indexOf('RT-mix.wav') > -1
+ RecordingActions.mixTransferred()
}
)
diff --git a/web/app/assets/javascripts/react-components/stores/ChatStore.js.coffee b/web/app/assets/javascripts/react-components/stores/ChatStore.js.coffee
index e7a9d031f..bf6bbb719 100644
--- a/web/app/assets/javascripts/react-components/stores/ChatStore.js.coffee
+++ b/web/app/assets/javascripts/react-components/stores/ChatStore.js.coffee
@@ -81,7 +81,7 @@ SessionStore = context.SessionStore
query.start = next
return query
- c: (lessonSessionId) ->
+ initializeLesson: (lessonSessionId) ->
@lessonSessionId = lessonSessionId
@channelType = 'lesson'
diff --git a/web/app/assets/javascripts/react-components/stores/MixerStore.js.coffee b/web/app/assets/javascripts/react-components/stores/MixerStore.js.coffee
index d98600ca5..7518f8735 100644
--- a/web/app/assets/javascripts/react-components/stores/MixerStore.js.coffee
+++ b/web/app/assets/javascripts/react-components/stores/MixerStore.js.coffee
@@ -20,6 +20,7 @@ rest = context.JK.Rest()
checkingMissingPeers : {}
missingMixerPeers : {}
recheckTimeout : null
+ clientsWithAudioOverride : {}
init: ->
# Register with the app store to get @app
@@ -38,6 +39,7 @@ rest = context.JK.Rest()
this.listenTo(context.MixerActions.metronomeChanged, this.onMetronomeChanged)
this.listenTo(context.MixerActions.deadUserRemove, this.onDeadUserRemove)
this.listenTo(context.MixerActions.missingPeerMixer, this.onMissingPeerMixer)
+ this.listenTo(context.MixerActions.clientsWithAudio, this.onClientsWithAudio)
context.JK.HandleVolumeChangeCallback2 = @handleVolumeChangeCallback
context.JK.HandleMetronomeCallback2 = @handleMetronomeCallback
@@ -76,7 +78,7 @@ rest = context.JK.Rest()
# metroSound = args.sound
SessionActions.syncWithServer()
- @mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @mixers?.mixMode || MIX_MODES.PERSONAL)
+ @mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @clientsWithAudioOverride, @mixers?.mixMode || MIX_MODES.PERSONAL)
@issueChange()
@@ -132,7 +134,7 @@ rest = context.JK.Rest()
@masterMixers = context.jamClient.SessionGetAllControlState(true);
@personalMixers = context.jamClient.SessionGetAllControlState(false);
- @mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @mixers?.mixMode || MIX_MODES.PERSONAL)
+ @mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @clientsWithAudioOverride, @mixers?.mixMode || MIX_MODES.PERSONAL)
@issueChange()
@@ -146,9 +148,9 @@ rest = context.JK.Rest()
# simulate a state change to cause a UI redraw
@issueChange()
- onFaderChanged: (data, mixers, gainType) ->
+ onFaderChanged: (data, mixers, gainType, controlGroup) ->
- @mixers.faderChanged(data, mixers, gainType)
+ @mixers.faderChanged(data, mixers, gainType, controlGroup)
@issueChange()
@@ -171,7 +173,7 @@ rest = context.JK.Rest()
@metro.sound = sound
context.jamClient.SessionSetMetronome(@metro.tempo, @metro.sound, 1, 0);
- @mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @mixers?.mixMode || MIX_MODES.PERSONAL)
+ @mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @clientsWithAudioOverride, @mixers?.mixMode || MIX_MODES.PERSONAL)
@issueChange()
onDeadUserRemove: (clientId) ->
@@ -193,6 +195,9 @@ rest = context.JK.Rest()
@issueChange()
+ onClientsWithAudio: (clients) ->
+ @clientsWithAudioOverride = clients
+
onMissingPeerMixer: (clientId) ->
missingPeerAttempts = @missingMixerPeers[clientId]
@@ -219,7 +224,7 @@ rest = context.JK.Rest()
@masterMixers = context.jamClient.SessionGetAllControlState(true);
@personalMixers = context.jamClient.SessionGetAllControlState(false);
logger.debug("MixerStore: recheckForMixers")
- @mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @mixers?.mixMode || MIX_MODES.PERSONAL)
+ @mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @clientsWithAudioOverride, @mixers?.mixMode || MIX_MODES.PERSONAL)
@issueChange()
onInitGain: (mixer) ->
@@ -234,7 +239,7 @@ rest = context.JK.Rest()
logger.debug("MixerStore: onMixersChanged")
- @mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @mixers?.mixMode || MIX_MODES.PERSONAL)
+ @mixers = new context.MixerHelper(@session, @masterMixers, @personalMixers, @metro, @noAudioUsers, @clientsWithAudioOverride, @mixers?.mixMode || MIX_MODES.PERSONAL)
SessionActions.mixersChanged.trigger(type, text, @mixers.getTrackInfo())
diff --git a/web/app/assets/javascripts/react-components/stores/RecordingStore.js.jsx.coffee b/web/app/assets/javascripts/react-components/stores/RecordingStore.js.jsx.coffee
index 682d8744e..70e49ee33 100644
--- a/web/app/assets/javascripts/react-components/stores/RecordingStore.js.jsx.coffee
+++ b/web/app/assets/javascripts/react-components/stores/RecordingStore.js.jsx.coffee
@@ -33,10 +33,11 @@ BackendToFrontendFPS = {
this.trigger({isRecording: @recordingModel.isRecording()})
onStartRecording: (recordVideo, recordChat) ->
-
- frameRate = context.jamClient.GetCurrentVideoFrameRate() || 0;
-
- frameRate = BackendToFrontendFPS[frameRate]
+ frameRate = 0
+ if recordVideo
+ if context.jamClient.GetCurrentVideoFrameRate?
+ frameRate = context.jamClient.GetCurrentVideoFrameRate() || 0;
+ frameRate = BackendToFrontendFPS[frameRate]
NoVideoRecordActive = 0
WebCamRecordActive = 1
@@ -49,12 +50,14 @@ BackendToFrontendFPS = {
onStartingRecording: (details) ->
details.cause = 'starting'
+ @mixTransferred = false
this.trigger(details)
@popupRecordingControls() unless @recordingWindow?
onStartedRecording: (details) ->
details.cause = 'started'
+ @mixTransferred = false
this.trigger(details)
@popupRecordingControls() unless @recordingWindow?
@@ -92,6 +95,9 @@ BackendToFrontendFPS = {
logger.debug("recording controls closed")
@recordingWindow = null
+ onMixTransferred: () ->
+ @mixTransferred = true
+
popupRecordingControls: () ->
logger.debug("poupRecordingControls")
@recordingWindow = window.open("/popups/recording-controls", 'Recording', 'scrollbars=yes,toolbar=no,status=no,height=315,width=340')
diff --git a/web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee b/web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee
index 00439ff3c..30c8e2f26 100644
--- a/web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee
+++ b/web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee
@@ -6,6 +6,7 @@ EVENTS = context.JK.EVENTS
MIX_MODES = context.JK.MIX_MODES
SessionActions = @SessionActions
+MixerActions = @MixerActions
SessionStatThresholds = gon.session_stat_thresholds
NetworkThresholds = SessionStatThresholds.network
@@ -16,7 +17,9 @@ AggregateThresholds = SessionStatThresholds.aggregate
@SessionStatsStore = Reflux.createStore(
{
listenables: @SessionStatsActions
- rawStats: null
+ rawStats: null,
+ parentStats: null
+ clientsWithAudio: null
init: ->
# Register with the app store to get @app
@@ -24,8 +27,9 @@ AggregateThresholds = SessionStatThresholds.aggregate
onAppInit: (@app) ->
- onPushStats: (stats) ->
+ onPushStats: (stats, parentStats) ->
@rawStats = stats
+ @parentStats = parentStats
@changed()
classify: (holder, field, threshold) ->
@@ -65,17 +69,15 @@ AggregateThresholds = SessionStatThresholds.aggregate
else
holder[fieldLevel] = 'good'
-
- changed: () ->
- @stats = {}
-
+ classifyStats: (statBag) ->
+ container = {}
self = null
- for participant in @rawStats
+ for participant in statBag
if participant.id == @app.clientId
self = participant
break
- for participant in @rawStats
+ for participant in statBag
aggregate = {}
@@ -93,6 +95,9 @@ AggregateThresholds = SessionStatThresholds.aggregate
network = participant.network
if network?
+ if network.audio_bitrate_rx > 0 && network.audio_bitrate_tx > 0
+ @clientsWithAudio[participant.id] = true
+
@classify(network, 'audiojq_median', NetworkThresholds)
@classify(network, 'jitter_var', NetworkThresholds)
@classify(network, 'audio_bitrate_rx', NetworkThresholds)
@@ -151,8 +156,23 @@ AggregateThresholds = SessionStatThresholds.aggregate
else
participant.classification = 'unknown'
- @stats[participant.id] = participant
+ container[participant.id] = participant
+ return container
+
+ changed: () ->
+ @clientsWithAudio = {}
+ @stats = {}
+
+ @stats = @classifyStats(@rawStats)
+ if @parentStats
+ @stats.parent = @classifyStats(@parentStats)
+ else
+ @stats.parent = null
+
+ MixerActions.clientsWithAudio(@clientsWithAudio)
+
+ # see if we can reset noAudio
@trigger(@stats)
}
)
\ No newline at end of file
diff --git a/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee b/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee
index 139c3b1d5..4c4b564ec 100644
--- a/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee
+++ b/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee
@@ -728,8 +728,19 @@ ConfigureTracksActions = @ConfigureTracksActions
context.jamClient.SessionRegisterCallback("JK.HandleBridgeCallback2");
context.jamClient.RegisterRecordingCallbacks("JK.HandleRecordingStartResult", "JK.HandleRecordingStopResult", "JK.HandleRecordingStarted", "JK.HandleRecordingStopped", "JK.HandleRecordingAborted");
context.jamClient.SessionSetConnectionStatusRefreshRate(1000);
+ clientRole = context.jamClient.getClientParentChildRole();
+ parentClientId = context.jamClient.getParentClientId();
+ logger.debug("role when joining session: #{clientRole}, parent client id #{parentClientId}")
#context.JK.HelpBubbleHelper.jamtrackGuideSession($screen.find('li.open-a-jamtrack'), $screen)
+ if clientRole == 0
+ clientRole = 'child'
+ else if clientRole == 1
+ clientRole = 'parent'
+
+ if clientRole == '' || !clientRole
+ clientRole = null
+
# subscribe to events from the recording model
@recordingRegistration()
@@ -741,6 +752,8 @@ ConfigureTracksActions = @ConfigureTracksActions
as_musician: true,
tracks: @userTracks,
session_id: @currentSessionId,
+ client_role: clientRole,
+ parent_client_id: parentClientId
audio_latency: context.jamClient.FTUEGetExpectedLatency().latency
})
.done((response) =>
@@ -840,8 +853,11 @@ ConfigureTracksActions = @ConfigureTracksActions
@backendStatsInterval = window.setInterval((() => (@updateBackendStats())), 1000)
updateBackendStats: () ->
- connectionStats = window.jamClient.getConnectionDetail('')
- SessionStatsActions.pushStats(connectionStats)
+ connectionStats = window.jamClient.getConnectionDetail('', false)
+ parentConnectionStats = window.jamClient.getConnectionDetail('', true)
+ #console.log("CONNECTION STATES", connectionStats)
+ #console.log("PARENT STATES", parentConnectionStats)
+ SessionStatsActions.pushStats(connectionStats, parentConnectionStats)
trackChanges: (header, payload) ->
if @currentTrackChanges < payload.track_changes_counter
diff --git a/web/app/assets/javascripts/recordingModel.js b/web/app/assets/javascripts/recordingModel.js
index 61114fdb8..18fbebff8 100644
--- a/web/app/assets/javascripts/recordingModel.js
+++ b/web/app/assets/javascripts/recordingModel.js
@@ -102,7 +102,7 @@
var details = { clientId: app.clientId, reason: 'rest', detail: arguments, isRecording: false }
$self.triggerHandler('startedRecording', details);
currentlyRecording = false;
- context.RecordingActions.startedRecording(details);
+ context.RecordingActions.startedRecording(details);
})
diff --git a/web/app/assets/javascripts/redeem_complete.js b/web/app/assets/javascripts/redeem_complete.js
index d5bb834e2..30f62c567 100644
--- a/web/app/assets/javascripts/redeem_complete.js
+++ b/web/app/assets/javascripts/redeem_complete.js
@@ -28,6 +28,7 @@
var $downloadApplicationLink = null;
var $noPurchasesPrompt = null;
var shoppingCartItem = null;
+ var $startUsingJtPopup = null;
function beforeShow() {
@@ -156,6 +157,12 @@
}
})
}
+
+
+ console.log("jamtracks", jamTracks)
+ $startUsingJtPopup.attr('href', '/popups/jamtrack-player/' + jamTracks[0].id)
+ $startUsingJtPopup.find('.download-jamkazam').html('Click Here to Start Using ' + jamTracks[0].name + '')
+ context.JK.popExternalLinks($startUsingJtPopup.parent())
}
}
@@ -246,6 +253,7 @@
$backBtn = $screen.find('.back');
$downloadApplicationLink = $screen.find('.download-jamkazam-wrapper');
$noPurchasesPrompt = $screen.find('.no-purchases-prompt')
+ $startUsingJtPopup = $screen.find('.jt-popup')
if ($screen.length == 0) throw "$screen must be specified";
diff --git a/web/app/assets/javascripts/web/web.js b/web/app/assets/javascripts/web/web.js
index 2f2917a2d..38f03af42 100644
--- a/web/app/assets/javascripts/web/web.js
+++ b/web/app/assets/javascripts/web/web.js
@@ -9,7 +9,7 @@
//= require jquery.queryparams
//= require jquery.hoverIntent
//= require jquery.cookie
-//= require jquery.clipboard
+//= require clipboard
//= require jquery.easydropdown
//= require jquery.carousel-1.1
//= require jquery.mousewheel-3.1.9
diff --git a/web/app/assets/javascripts/wizard/gear/step_select_gear.js b/web/app/assets/javascripts/wizard/gear/step_select_gear.js
index a73274ab4..3d3187bed 100644
--- a/web/app/assets/javascripts/wizard/gear/step_select_gear.js
+++ b/web/app/assets/javascripts/wizard/gear/step_select_gear.js
@@ -867,6 +867,8 @@
else {
var inputSampleRate = 44100;
}
+ // ignore all this; just set it to
+ inputSampleRate = 'DEVICE_DEFAULT'
logger.debug("applying the sample rate based on input device: " + selectedDeviceInfo.input.id + " (" + inputSampleRate + ")");
sampleRate.selectSampleRate(inputSampleRate);
context.jamClient.FTUESetPreferredMixerSampleRate(sampleRate.selectedSampleRate());
diff --git a/web/app/assets/javascripts/wizard/sample_rate.js b/web/app/assets/javascripts/wizard/sample_rate.js
index 2a828ff14..e59ef403c 100644
--- a/web/app/assets/javascripts/wizard/sample_rate.js
+++ b/web/app/assets/javascripts/wizard/sample_rate.js
@@ -46,6 +46,9 @@
else if(value == 96000) {
setter = 'PREFER_96'
}
+ else if (value == 'DEVICE_DEFAULT') {
+ setter = 'USE_DEVICE_DEFAULT_SR'
+ }
console.log("SELECT SAMPLE RATE" + value, setter);
context.JK.dropdown($sampleRate.val(setter).easyDropDown('select', setter.toString(), true))
}
@@ -63,7 +66,7 @@
}
function resetValues() {
- $sampleRate.val('PREFER_44').easyDropDown('select', 'PREFER_44', true)
+ $sampleRate.val('USE_DEVICE_DEFAULT_SR').easyDropDown('select', 'USE_DEVICE_DEFAULT_SR', true)
}
diff --git a/web/app/assets/stylesheets/client/checkout_payment.scss b/web/app/assets/stylesheets/client/checkout_payment.scss
index c1f261031..1485e86af 100644
--- a/web/app/assets/stylesheets/client/checkout_payment.scss
+++ b/web/app/assets/stylesheets/client/checkout_payment.scss
@@ -33,6 +33,24 @@
}
}
+ .paypal-region {
+ text-align: center;
+ margin:10px auto 0;
+ /**margin: 10px auto 0;
+ padding: 10px 10px 5px;
+ background-color: white;
+ border-radius: 8px;
+ border-color: #ccc;
+ border-style: solid;
+ border-width: 3px;
+ width:145px;*/
+ }
+
+ .or-text {
+ margin: 60px auto 0;
+ text-align:center;
+ }
+
h2 {
color:white;
background-color:#4d4d4d;
diff --git a/web/app/assets/stylesheets/client/jamtrackSearch.scss b/web/app/assets/stylesheets/client/jamtrackSearch.scss
index cd23a55ab..45e9230ab 100644
--- a/web/app/assets/stylesheets/client/jamtrackSearch.scss
+++ b/web/app/assets/stylesheets/client/jamtrackSearch.scss
@@ -266,9 +266,19 @@
margin-top: 5px;
}
+ .jamtrack-variant-help {
+ margin-bottom:20px;
+ }
+ .jamtrack-add-zone {
+ margin: 8px 0px;
+ position:relative;
+ }
.jamtrack-price {
- margin-top: 5px;
+ width:100%;
+ margin: 0 auto 10px;
font-size: 20px;
+ color:white;
+ display:block;
&.free {
margin-top:0;
@@ -282,7 +292,7 @@
}
.jamtrack-add-cart, .jamtrack-add-cart-disabled {
- margin: 8px 0px;
+ position:relative;
}
.jamtrack-license {
diff --git a/web/app/assets/stylesheets/client/react-components/AccountRetailerScreen.scss b/web/app/assets/stylesheets/client/react-components/AccountRetailerScreen.scss
index 58134ce83..59cd64950 100644
--- a/web/app/assets/stylesheets/client/react-components/AccountRetailerScreen.scss
+++ b/web/app/assets/stylesheets/client/react-components/AccountRetailerScreen.scss
@@ -46,6 +46,9 @@
.scooter {
margin-bottom:10px;
}
+ div.retailer-split {
+ margin-top:10px;
+ }
.store-header {
float: left;
padding-top: 10px;
@@ -280,4 +283,10 @@
font-size:12px;
margin:0;
}
+
+ .split-input {
+ :after {
+ content: '%';
+ }
+ }
}
diff --git a/web/app/assets/stylesheets/client/react-components/PayPalConfirmationScreen.scss b/web/app/assets/stylesheets/client/react-components/PayPalConfirmationScreen.scss
new file mode 100644
index 000000000..04403000f
--- /dev/null
+++ b/web/app/assets/stylesheets/client/react-components/PayPalConfirmationScreen.scss
@@ -0,0 +1,78 @@
+@import "client/common.scss";
+
+[data-react-class="PayPalConfirmationScreen"] {
+ height: 100%;
+ overflow: scroll;
+
+ .content-body-scroller {
+ height: calc(100% - 30px) ! important; // 15px top and bottom padding, and 48px used by .controls
+ padding: 15px 30px;
+ }
+
+ .confirm-header {
+ color: white;
+ font-size: 20px;
+ }
+
+ .controls.bottom {
+ margin-top: 20px;
+ }
+
+ .place-order-btn {
+ text-align: center;
+ margin-right:0;
+ }
+
+ .or-holder {
+ margin-top: 20px;
+ text-align: center;
+ }
+
+ .cancel-order-btn {
+ margin-top: 20px;
+ text-align: center;
+ }
+ .shopping-cart-contents {
+ @include border-box_sizing;
+ width: 50%;
+ margin-top:20px;
+ }
+ .controls {
+ @include border-box_sizing;
+ width:50%;
+ a {
+ float:right;
+ }
+ }
+ .loading-indicator {
+ margin-bottom:20px;
+ padding-bottom:20px;
+ }
+
+ .sold-notice {
+ h2 {
+ font-size:30px;
+ text-align:center;
+ }
+ }
+ .download-jamkazam {
+ color:$ColorLink;
+ border-radius: 4px;
+ border-style:solid;
+ border-color:#AAA;
+ border-width:1px;
+ padding:10px;
+ margin-top:20px;
+ display:inline-block;
+ }
+
+ .download-jamkazam-wrapper, .back-to-browsing {
+ text-align:center;
+ display:block;
+ margin-top:35px;
+
+ &.hidden {
+ display:none;
+ }
+ }
+}
diff --git a/web/app/assets/stylesheets/client/react-components/ShoppingCartContents.scss b/web/app/assets/stylesheets/client/react-components/ShoppingCartContents.scss
new file mode 100644
index 000000000..64a37bd95
--- /dev/null
+++ b/web/app/assets/stylesheets/client/react-components/ShoppingCartContents.scss
@@ -0,0 +1,64 @@
+@import "client/common.scss";
+
+.shopping-cart-contents {
+
+ background-color:#262626;
+ border-width:0 1px 0 0;
+ border-style:solid;
+ border-color:#333;
+ padding:20px 20px 0;
+ .cart-item-caption {
+ width: 50%;
+ text-align: left;
+ float: left;
+ margin-bottom: 10px;
+ @include border_box_sizing;
+ }
+
+ .first-one-free {
+ font-size: 14px;
+ font-style: italic;
+ margin-left: 15px;
+ }
+
+ .cart-item-price {
+ width: 25%;
+ text-align: right;
+ float: left;
+ padding: 0 10px;
+ margin-bottom: 10px;
+ @include border_box_sizing;
+ }
+
+ .cart-item-quantity {
+ width: 10%;
+ text-align: right;
+ float: left;
+ padding: 0 10px;
+ margin-bottom: 10px;
+ @include border_box_sizing;
+ }
+
+ .cart-items {
+ margin-top: 10px;
+ padding-left: 10px;
+ }
+
+ .cart-item {
+ margin-top: 10px;
+ }
+
+ .no-cart-items {
+ }
+
+ .tax-total {
+ margin-top:10px;
+ border-width:1px 0 0 0;
+ border-color:white;
+ border-style:solid;
+ padding-top:10px;
+ }
+ .cart-item.total {
+ margin-top:5px;
+ }
+}
diff --git a/web/app/assets/stylesheets/client/react-components/TeacherProfile.scss b/web/app/assets/stylesheets/client/react-components/TeacherProfile.scss
index 3a4618776..b1f2fbcec 100644
--- a/web/app/assets/stylesheets/client/react-components/TeacherProfile.scss
+++ b/web/app/assets/stylesheets/client/react-components/TeacherProfile.scss
@@ -199,6 +199,9 @@
position:absolute;
}
+ .copy-profile-link {
+ float:right;
+ }
.spinner-large {
width:200px;
diff --git a/web/app/assets/stylesheets/client/wizard/gearWizard.scss b/web/app/assets/stylesheets/client/wizard/gearWizard.scss
index a9f52c02d..b84fb7f5c 100644
--- a/web/app/assets/stylesheets/client/wizard/gearWizard.scss
+++ b/web/app/assets/stylesheets/client/wizard/gearWizard.scss
@@ -723,7 +723,7 @@
}
.easydropdown-wrapper {
width:auto;
- float:right;
+ float:left;
}
}
diff --git a/web/app/assets/stylesheets/landing/landing.css b/web/app/assets/stylesheets/landing/landing.css
index d18d95761..3017405c6 100644
--- a/web/app/assets/stylesheets/landing/landing.css
+++ b/web/app/assets/stylesheets/landing/landing.css
@@ -12,4 +12,6 @@
*= require dialogs/dialog
*= require icheck/minimal/minimal
*= require landings/posa_activation
+*= require landings/simple_jamtracks
+*= require landings/simple_jamclass
*/
\ No newline at end of file
diff --git a/web/app/assets/stylesheets/landings/simple_jamclass.scss b/web/app/assets/stylesheets/landings/simple_jamclass.scss
new file mode 100644
index 000000000..63d472f86
--- /dev/null
+++ b/web/app/assets/stylesheets/landings/simple_jamclass.scss
@@ -0,0 +1,29 @@
+@import "client/common.scss";
+
+body.landing_page.full.simple_jamclass {
+
+ .logo-home {
+ left: 50%;
+ margin-left: -149px;
+ width: 298px;
+ position: relative;
+ }
+
+ div.wrapper {
+ width:100%;
+ max-width:1100px;
+ }
+
+ .landing-content {
+
+ font-size: 1rem;
+
+ .video-wrapper {
+ padding-top: 30px;
+ }
+ .video-container {
+ margin-left: 12.75%;
+ width: 75%;
+ }
+ }
+}
\ No newline at end of file
diff --git a/web/app/assets/stylesheets/landings/simple_jamtracks.scss b/web/app/assets/stylesheets/landings/simple_jamtracks.scss
new file mode 100644
index 000000000..831de8807
--- /dev/null
+++ b/web/app/assets/stylesheets/landings/simple_jamtracks.scss
@@ -0,0 +1,29 @@
+@import "client/common.scss";
+
+body.landing_page.full.simple_jamtracks {
+
+ .logo-home {
+ left: 50%;
+ margin-left: -149px;
+ width: 298px;
+ position: relative;
+ }
+
+ div.wrapper {
+ width:100%;
+ max-width:1100px;
+ }
+
+ .landing-content {
+
+ font-size: 1rem;
+
+ .video-wrapper {
+ padding-top: 30px;
+ }
+ .video-container {
+ margin-left: 12.75%;
+ width: 75%;
+ }
+ }
+}
\ No newline at end of file
diff --git a/web/app/assets/stylesheets/minimal/jamtrack_player.scss b/web/app/assets/stylesheets/minimal/jamtrack_player.scss
index 6a9989981..74e4f5fb2 100644
--- a/web/app/assets/stylesheets/minimal/jamtrack_player.scss
+++ b/web/app/assets/stylesheets/minimal/jamtrack_player.scss
@@ -9,15 +9,22 @@ body.jamtrack-player-popup.popup {
padding-top:20px !important;
.wrapper {
- width: 450px;
+ width: 840px;
position: relative;
margin: 0px auto;
- padding: 0 10px 10px;
+ //padding: 0 10px 10px;
+ }
+
+ .jamtrack-player-controls {
border-width: 1px;
border-color: #ed3618;
border-style: solid;
+ width: 450px;
+ position: relative;
+ float:left;
}
+
#minimal-container {
padding-bottom:0;
}
@@ -26,10 +33,35 @@ body.jamtrack-player-popup.popup {
text-decoration: underline;
}
+ .media-header {
+ background-color: #ed3618;;
+ color:white;
+ font-size:20px;
+ text-align:left;
+ padding:7px 15px;
+ }
+
.media-controls-popup {
padding:15px 15px 3px 15px;
}
+ .helpful-resources {
+ float:right;
+ margin-left:60px;
+ width:285px;
+ color:#cccccc;
+ text-align:left;
+ }
+
+ .helpful-header {
+ margin-top:7px;
+ font-size:20px;
+ margin-bottom:20px;
+ }
+
+ .helpful-section {
+ margin-bottom:20px;
+ }
.field {
margin-top:20px;
}
diff --git a/web/app/controllers/api_jam_tracks_controller.rb b/web/app/controllers/api_jam_tracks_controller.rb
index f16c5f00c..13a6723a6 100644
--- a/web/app/controllers/api_jam_tracks_controller.rb
+++ b/web/app/controllers/api_jam_tracks_controller.rb
@@ -164,6 +164,11 @@ class ApiJamTracksController < ApiController
end
if params[:download]
+ if !@jam_track_right.can_download && params[:client_type] != 'ios' # let iOS slip through when passing in download param
+ render :json => { :message => "Download rights not purchased"}, :status => 403
+ return
+ end
+
if DownloadTracker.check(current_user, request.remote_ip, jam_track_track, !@jam_track_right.redeemed, params[:mark], false)
render :json => { :message => "IP blacklisted"}, :status => 403
return
@@ -323,20 +328,12 @@ class ApiJamTracksController < ApiController
def ios_order_placed
jam_track = JamTrack.find(params[:jam_track_id])
- jam_track_right = jam_track.right_for_user(current_user)
-
- # the user already owns this JamTrac, so just short-circuit out
- if jam_track_right
- response = {name: jam_track.name, id: jam_track.id, jam_track_right_id: jam_track_right.id, version: jam_track.version}
- render :json => response, :status => 200
- return
- end
-
begin
Sale.ios_purchase(current_user,
jam_track,
params[:receipt],
- params[:price_data])
+ params[:price_data],
+ params[:variant])
rescue
response = { message: $!.to_s }
render :json => response, :status => 422
diff --git a/web/app/controllers/api_jamblasters_controller.rb b/web/app/controllers/api_jamblasters_controller.rb
index 69b6d7b91..082048cba 100644
--- a/web/app/controllers/api_jamblasters_controller.rb
+++ b/web/app/controllers/api_jamblasters_controller.rb
@@ -51,10 +51,10 @@ class ApiJamblastersController < ApiController
return
end
- if jamblaster && jamblaster.users.length > 0 && !user.jamblasters.include?(jamblaster)
- render :json => {reason: "jamblaster_access", message: "current user does not have access to jamblaster #{jamblaster.id}"}, status: 403
- return
- end
+# if jamblaster && jamblaster.users.length > 0 && !user.jamblasters.include?(jamblaster)
+# render :json => {reason: "jamblaster_access", message: "current user does not have access to jamblaster #{jamblaster.id}"}, status: 403
+# return
+# end
render :json => {}, status: 200
end
@@ -80,16 +80,18 @@ class ApiJamblastersController < ApiController
def start_pairing
jamblaster = Jamblaster.find_by_client_id(params[:jbid])
- if jamblaster && jamblaster.users.length > 0 && !current_user.jamblasters.include?(jamblaster)
- render :json => {reason: "jamblaster_access", message: "current user does not have access to jamblaster #{jamblaster.id}"}, status: 403
- return
- end
+ # allow multiple users to own a single jb
+ #if jamblaster && jamblaster.users.length > 0 && !current_user.jamblasters.include?(jamblaster)
+ # render :json => {reason: "jamblaster_access", message: "current user does not have access to jamblaster #{jamblaster.id}"}, status: 403
+ # return
+ #end
@pairing = JamblasterPairingRequest.new
@pairing.user = current_user
@pairing.jamblaster_client_id = params[:jbid]
@pairing.jamblaster = jamblaster
@pairing.vtoken = params[:vtoken]
+ puts "@PAIRING #{@pairing.inspect}"
if !@pairing.save
respond_with_model(@pairing)
else
@@ -165,10 +167,11 @@ class ApiJamblastersController < ApiController
return
end
- if jamblaster.users.length > 0 && !pairing_request.user.jamblasters.include?(jamblaster)
- render :json => {reason: "jamblaster_access", message: "current user does not have access to jamblaster #{jamblaster.id} with vtoken #{vtoken}"}, status: 403
- return
- end
+ # allow multiples users to control a single jamblaster
+ #if jamblaster.users.length > 0 && !pairing_request.user.jamblasters.include?(jamblaster)
+ # render :json => {reason: "jamblaster_access", message: "current user does not have access to jamblaster #{jamblaster.id} with vtoken #{vtoken}"}, status: 403
+ # return
+ #end
if !jamblaster.users.include?(pairing_request.user)
jamblaster.users << pairing_request.user
diff --git a/web/app/controllers/api_lesson_sessions_controller.rb b/web/app/controllers/api_lesson_sessions_controller.rb
index 3fcd377a0..2a74b147f 100644
--- a/web/app/controllers/api_lesson_sessions_controller.rb
+++ b/web/app/controllers/api_lesson_sessions_controller.rb
@@ -15,7 +15,6 @@ class ApiLessonSessionsController < ApiController
render "api_lesson_sessions/index", :layout => nil
end
-
def show
end
@@ -72,13 +71,13 @@ class ApiLessonSessionsController < ApiController
if params[:update_all]
# check if the next scheduled lesson is doable
- if 24.hours.from_now > @lesson_session.lesson_booking.next_lesson.music_session.scheduled_start
+ if 15.minutes.from_now > @lesson_session.lesson_booking.next_lesson.music_session.scheduled_start
response = {message: 'time_limit'}
render :json => response, :status => 422
return
end
else
- if 24.hours.from_now > @lesson_session.music_session.scheduled_start
+ if 15.minutes.from_now > @lesson_session.music_session.scheduled_start
response = {message: 'time_limit'}
render :json => response, :status => 422
return
diff --git a/web/app/controllers/api_music_sessions_controller.rb b/web/app/controllers/api_music_sessions_controller.rb
index 6ee432d4e..35d3111e2 100644
--- a/web/app/controllers/api_music_sessions_controller.rb
+++ b/web/app/controllers/api_music_sessions_controller.rb
@@ -297,7 +297,9 @@ class ApiMusicSessionsController < ApiController
params[:client_id],
params[:as_musician],
params[:tracks],
- params[:audio_latency]
+ params[:audio_latency],
+ params[:client_role],
+ params[:parent_client_id]
)
if @connection.errors.any?
diff --git a/web/app/controllers/api_pay_pal_controller.rb b/web/app/controllers/api_pay_pal_controller.rb
new file mode 100644
index 000000000..8666cfd13
--- /dev/null
+++ b/web/app/controllers/api_pay_pal_controller.rb
@@ -0,0 +1,136 @@
+class ApiPayPalController < ApiController
+
+ before_filter :api_signed_in_user
+
+ respond_to :json
+
+
+ def log
+ @log || Logging.logger[VanillaForumsController]
+ end
+
+ def start_checkout
+ cancel_path = params[:path] ? params[:path] : ERB::Util.url_encode('/client#/checkoutPayment')
+
+ tax = true
+ tax_rate = tax ? 0.0825 : 0
+ total = current_user.shopping_cart_total.round(2)
+ tax_total = (total * tax_rate).round(2)
+ total = total + tax_total
+ total = total.round(2)
+
+
+ @api = PayPal::SDK::Merchant::API.new
+ @set_express_checkout = @api.build_set_express_checkout(
+ {
+ :Version => "117.0",
+ :SetExpressCheckoutRequestDetails =>
+ {
+ :ReturnURL => ApplicationHelper.base_uri(request) + '/auth/paypal/checkout',
+ :CancelURL => ApplicationHelper.base_uri(request) + '/auth/paypal/checkout?cancel=1&path=' + cancel_path,
+ # :NoShipping => "1",
+ # :ReqConfirmShipping => "0",
+ # :ReqBillingAddress => "1",
+ :PaymentDetails =>
+ [
+ {
+ :OrderTotal => {
+ :currencyID => "USD",
+ :value => total
+ },
+ :PaymentAction => "Sale"
+ }
+ ]
+ }
+ }
+ )
+ @set_express_checkout_response = @api.set_express_checkout(@set_express_checkout)
+
+ log.info("User #{current_user.email}, SetExpressCheckout #{@set_express_checkout_response.inspect}")
+
+ if @set_express_checkout_response.Ack == 'Failure'
+ render json: {message: @set_express_checkout_response.Errors[0].LongMessage}, status: 422
+ return
+ end
+
+ redirect_to Rails.configuration.paypal_express_url + '&token=' + ERB::Util.url_encode(@set_express_checkout_response.Token)
+ end
+
+ # called by frontend after the user comes back from initial express page
+ def checkout_detail
+ # here we can see if they will pay tax
+
+ if !current_user.has_paypal_auth?
+ render json: {}, :status => 404
+ return
+ end
+ paypal_auth = current_user.paypal_auth
+
+ @api = PayPal::SDK::Merchant::API.new
+ @get_express_checkout_details = @api.build_get_express_checkout_details({:Token => paypal_auth.token})
+ @response = @api.get_express_checkout_details(@get_express_checkout_details)
+
+ puts @response.inspect
+ tax = false
+ if @response.Ack == 'Success'
+ payerInfo = @response.GetExpressCheckoutDetailsResponseDetails.PayerInfo
+ if payerInfo.Address && ( payerInfo.Address.Country == 'US' && payerInfo.Address.StateOrProvince == 'TX')
+ # we need to ask for taxes
+ tax = true
+ end
+ else
+ render json: {message: @response.Errors[0].LongMessage}, status: 422
+ return
+ end
+
+ log.debug("User #{current_user.email}, GetExpressCheckout: #{@get_express_checkout_details_response.inspect}")
+
+ render json: {tax: tax}
+ end
+
+ # called by frontend when the user selects finally 'confirm purchase' (PLACE ORDER btn)
+ def confirm_purchase
+ if !current_user.has_paypal_auth?
+ render json: {}, :status => 404
+ return
+ end
+
+ error = nil
+ response = {jam_tracks: [], gift_cards: []}
+
+ #if Sale.is_mixed(current_user.shopping_carts)
+ # msg = "has free and non-free items. Try removing non-free items."
+ # render json: {message: "Cart " + msg, errors: {cart: [msg]}}, :status => 404
+ # return
+ #end
+
+ begin
+ sales = Sale.place_order(current_user, current_user.shopping_carts, true)
+ rescue RecurlyClientError => e
+ render json: {message: e.errors[:message]}, :status => 422
+ return
+ rescue PayPalClientError => x
+ render json: {message: x.errors[:message]}, :status => 422
+ return
+ end
+
+
+ sales.each do |sale|
+ sale.sale_line_items.each do |line_item|
+ if line_item.is_jam_track?
+ jam_track = line_item.product
+ jam_track_right = jam_track.right_for_user(current_user)
+ response[:jam_tracks] << {name: jam_track.name, id: jam_track.id, jam_track_right_id: jam_track_right.id, version: jam_track.version}
+ elsif line_item.is_gift_card?
+ gift_card = line_item.product
+ response[:gift_cards] << {name: gift_card.name, id: gift_card.id}
+ else
+ raise 'unknown sale line item type: ' + line_item.product_type
+ end
+ end
+ end
+
+ set_purchased_jamtrack_cookie
+ render :json => response, :status => 200
+ end
+end
\ No newline at end of file
diff --git a/web/app/controllers/api_shopping_carts_controller.rb b/web/app/controllers/api_shopping_carts_controller.rb
index e45429f86..42fc100fd 100644
--- a/web/app/controllers/api_shopping_carts_controller.rb
+++ b/web/app/controllers/api_shopping_carts_controller.rb
@@ -20,7 +20,7 @@ class ApiShoppingCartsController < ApiController
raise StateError, "Invalid JamTrack."
end
- @cart = ShoppingCart.add_jam_track_to_cart(any_user, jam_track, clear:params[:clear])
+ @cart = ShoppingCart.add_jam_track_to_cart(any_user, jam_track, params[:variant])
if @cart.errors.any?
response.status = :unprocessable_entity
diff --git a/web/app/controllers/artifacts_controller.rb b/web/app/controllers/artifacts_controller.rb
index e8f0b0635..9602dd565 100644
--- a/web/app/controllers/artifacts_controller.rb
+++ b/web/app/controllers/artifacts_controller.rb
@@ -4,23 +4,35 @@ class ArtifactsController < ApiController
# retrieve all available client downloads
def client_downloads
- clients = ArtifactUpdate.where("product like '%JamClient%' and environment = '#{ArtifactUpdate::DEFAULT_ENVIRONMENT}'").order(:product)
- if params[:type]
- clients = clients.where("product ilike 'JamClient/#{params[:type]}'")
+ # jamblaster specifies this params[:type]
+ is_jamblaster = params[:type] && params[:serialno]
- if params[:serialno]
- Jamblaster.bootstrap(params[:serialno])
+ if is_jamblaster
+ # check and see if there is a build just for this JB
+ clients = ArtifactUpdate.where('product ilike ? and environment = ?', "JamClient/#{params[:type]}", params[:serialno]).order(:product)
+ if clients.count == 0
+ # if not, then fine, give back the default environment
+ clients = ArtifactUpdate.where('product ilike ? and environment = ?', "JamClient/#{params[:type]}", ArtifactUpdate::DEFAULT_ENVIRONMENT).order(:product)
end
+ else
+ clients = ArtifactUpdate.where("product like '%JamClient%' and environment = '#{ArtifactUpdate::DEFAULT_ENVIRONMENT}'").order(:product)
end
+ if is_jamblaster && params[:serialno]
+ Jamblaster.bootstrap(params[:serialno])
+ end
+ #Parameters: {"serialno"=>"NCA-001-160602-00084", "version"=>"dev-20161120-153542", "type"=>"jamblaster"}
+ #Parameters: {"serialno"=>"NCA-001-160602-00084", "version"=>"dev-20161120-153542", "type"=>"jamblasterclient"}
result = {}
clients.each do |client|
url = client.determine_url
- if params[:type]
+
+ # jamblaster specifies this params[:type]
+ if is_jamblaster
result[params[:type]] = { :uri => url, :size => client.size, :version => client.version, :md5 => client.sha1}
else
result[client.product] = { :uri => url, :size => client.size, :version => client.version, :md5 => client.sha1}
diff --git a/web/app/controllers/landings_controller.rb b/web/app/controllers/landings_controller.rb
index bc56cea05..6be8dce62 100644
--- a/web/app/controllers/landings_controller.rb
+++ b/web/app/controllers/landings_controller.rb
@@ -451,5 +451,23 @@ class LandingsController < ApplicationController
@page_data = {retailer: @retailer, has_teachers: @retailer.teachers.count > 0}
render 'posa_activation', layout: 'web'
end
+
+ def simple_jamtracks
+ @description = 'JamTracks'
+ @no_landing_tag = true
+ @landing_tag_play_learn_earn = false
+ @responsive = true
+
+ render 'simple_jamtracks', layout: 'web'
+ end
+
+ def simple_jamclass
+ @description = 'JamClass'
+ @no_landing_tag = true
+ @landing_tag_play_learn_earn = false
+ @responsive = true
+
+ render 'simple_jamclass', layout: 'web'
+ end
end
diff --git a/web/app/controllers/sessions_controller.rb b/web/app/controllers/sessions_controller.rb
index 733573fa6..45541b03f 100644
--- a/web/app/controllers/sessions_controller.rb
+++ b/web/app/controllers/sessions_controller.rb
@@ -1,6 +1,8 @@
# this is not a jam session - this is an 'auth session'
class SessionsController < ApplicationController
+ before_filter :api_signed_in_user, only: :paypal_express_checkout
+
layout "web"
def signin
@@ -37,6 +39,42 @@ class SessionsController < ApplicationController
end
end
+ def paypal_express_checkout
+ # should get 'token' and 'PayerID' on success
+
+ # on failure, cancel=1
+
+ if params[:cancel] == '1' || params[:cancel] == 1
+ redirect_to params[:path] ? params[:path] : '/client#/jamtrack'
+ return
+ end
+
+ authorization = current_user.paypal_auth
+
+ # Always make and save a new authorization. This is because they expire, and honestly there's no cost
+ # to just making and saving it.
+
+ user_auth_hash = {
+ :provider => 'paypal',
+ :uid => params[:PayerID],
+ :token => params[:token],
+ :refresh_token => nil,
+ :token_expiration => 3.hours.from_now, # according to paypal docs, a token is good for 3 hours
+ :secret => nil
+ }
+
+ if authorization.nil?
+ authorization = current_user.user_authorizations.build(user_auth_hash)
+ authorization.save
+ else
+ authorization.token = user_auth_hash[:token]
+ authorization.token_expiration = user_auth_hash[:token_expiration]
+ authorization.uid = user_auth_hash[:uid]
+ authorization.save
+ end
+
+ redirect_to '/client#/paypal/confirm'
+ end
# OAuth docs
# http://net.tutsplus.com/tutorials/ruby/how-to-use-omniauth-to-authenticate-your-users/
diff --git a/web/app/controllers/vanilla_forums_controller.rb b/web/app/controllers/vanilla_forums_controller.rb
index 09733b288..2f16d3feb 100644
--- a/web/app/controllers/vanilla_forums_controller.rb
+++ b/web/app/controllers/vanilla_forums_controller.rb
@@ -52,7 +52,7 @@ class VanillaForumsController < ApplicationController
render :json => JsConnect::getJsConnectString(user, request,
- Rails.application.config.vanilla_client_id, Rails.application.config.vanilla_secret)
+ Rails.application.config.vanilla_client_id, Rails.application.config.vanilla_secret), :content_type => 'application/javascript'
end
diff --git a/web/app/views/api_jam_tracks/show.rabl b/web/app/views/api_jam_tracks/show.rabl
index d62bcebf2..839e26e64 100644
--- a/web/app/views/api_jam_tracks/show.rabl
+++ b/web/app/views/api_jam_tracks/show.rabl
@@ -1,6 +1,6 @@
object @jam_track
-attributes :id, :name, :description, :recording_type, :original_artist, :songwriter, :publisher, :sales_region, :price, :version, :duration, :year, :plan_code, :allow_free
+attributes :id, :name, :description, :recording_type, :original_artist, :songwriter, :publisher, :sales_region, :price, :version, :duration, :year, :plan_code, :allow_free, :download_price, :upgrade_price
node :genres do |item|
item.genres.select(:description).map(&:description)
@@ -10,6 +10,10 @@ node :added_cart do |item|
any_user.shopping_carts.where(cart_id: item.id).count != 0
end
+node :can_download do |item|
+ !!item.right_for_user(current_user, ShoppingCart::JAMTRACK_FULL)
+end
+
node :purchased do |item|
!!item.right_for_user(current_user)
end
diff --git a/web/app/views/api_jam_tracks/show_for_client.rabl b/web/app/views/api_jam_tracks/show_for_client.rabl
index 7705fc91a..9a528e03e 100644
--- a/web/app/views/api_jam_tracks/show_for_client.rabl
+++ b/web/app/views/api_jam_tracks/show_for_client.rabl
@@ -10,6 +10,10 @@ node :jmep do |jam_track|
jam_track.jmep_json ? jam_track.jmep_json : nil
end
+node :can_download do |item|
+ !!item.right_for_user(current_user, ShoppingCart::JAMTRACK_FULL)
+end
+
child(:jam_track_tracks => :tracks) {
attributes :id, :part, :instrument, :track_type, :position
diff --git a/web/app/views/api_jam_tracks/show_for_mobile.rabl b/web/app/views/api_jam_tracks/show_for_mobile.rabl
index 79b5183c7..dc68d2539 100644
--- a/web/app/views/api_jam_tracks/show_for_mobile.rabl
+++ b/web/app/views/api_jam_tracks/show_for_mobile.rabl
@@ -1,7 +1,11 @@
object @jam_track
-attributes :id, :name, :original_artist, :year, :genre_name
+attributes :id, :name, :original_artist, :year, :genre_name, :can_download
node :purchased_at do |jt|
Time.parse(jt.purchased_at).to_i rescue Time.now.to_i
end
+
+node :can_download do |item|
+ !!item.right_for_user(current_user, ShoppingCart::JAMTRACK_FULL)
+end
\ No newline at end of file
diff --git a/web/app/views/api_jam_tracks/show_jam_track_right.rabl b/web/app/views/api_jam_tracks/show_jam_track_right.rabl
index 365730236..2395670a5 100644
--- a/web/app/views/api_jam_tracks/show_jam_track_right.rabl
+++ b/web/app/views/api_jam_tracks/show_jam_track_right.rabl
@@ -1,3 +1,3 @@
object @jam_track_right
-attributes :id, :error_count, :error_reason, :error_detail, :signing_state, :packaging_steps, :current_packaging_step
\ No newline at end of file
+attributes :id, :error_count, :error_reason, :error_detail, :signing_state, :packaging_steps, :current_packaging_step, :can_download
\ No newline at end of file
diff --git a/web/app/views/api_music_sessions/show.rabl b/web/app/views/api_music_sessions/show.rabl
index 4fc73ea8a..2b931e40e 100644
--- a/web/app/views/api_music_sessions/show.rabl
+++ b/web/app/views/api_music_sessions/show.rabl
@@ -50,7 +50,7 @@ else
child(:connections => :participants) {
collection @music_sessions, :object_root => false
- attributes :ip_address, :client_id, :joined_session_at, :audio_latency, :id, :metronome_open, :is_jamblaster
+ attributes :ip_address, :client_id, :joined_session_at, :audio_latency, :id, :metronome_open, :is_jamblaster, :client_role, :parent_client_id
node :user do |connection|
{ :id => connection.user.id, :photo_url => connection.user.photo_url, :name => connection.user.name, :is_friend => connection.user.friends?(current_user), :connection_state => connection.aasm_state }
diff --git a/web/app/views/api_retailers/show.rabl b/web/app/views/api_retailers/show.rabl
index e8b5da7f3..1c9cd75e9 100644
--- a/web/app/views/api_retailers/show.rabl
+++ b/web/app/views/api_retailers/show.rabl
@@ -1,6 +1,6 @@
object @retailer
-attributes :id, :user_id, :name, :enabled, :original_fpfile, :cropped_fpfile, :crop_selection, :photo_url, :slug, :state, :city
+attributes :id, :user_id, :name, :enabled, :original_fpfile, :cropped_fpfile, :crop_selection, :photo_url, :slug, :state, :city, :payment_details
child :owner => :owner do
attributes :id, :email, :photo_url, :name, :first_name, :last_name
diff --git a/web/app/views/clients/_checkout_payment.html.slim b/web/app/views/clients/_checkout_payment.html.slim
index 5f1d1d323..e33e7521e 100644
--- a/web/app/views/clients/_checkout_payment.html.slim
+++ b/web/app/views/clients/_checkout_payment.html.slim
@@ -120,6 +120,13 @@ div layout="screen" layout-id="checkoutPayment" id="checkoutPaymentScreen" class
.divSaveCardHelper
label for="save-card" Save card for future use
.clearall
+
+ - if !Rails.application.config.paypal_admin_only || any_user.admin
+ .or-text or instead use:
+ .paypal-region
+ a href="/paypal/checkout/start" data-paypal-button="true"
+ img src="https://www.paypalobjects.com/webstatic/en_US/i/btn/png/gold-pill-paypalcheckout-34px.png" alt="PayPal Checkout"
+ a
.clearall
.clearall
.row.second
diff --git a/web/app/views/clients/_help.html.slim b/web/app/views/clients/_help.html.slim
index 344fbaaf8..c80c8f165 100644
--- a/web/app/views/clients/_help.html.slim
+++ b/web/app/views/clients/_help.html.slim
@@ -400,6 +400,15 @@ script type="text/template" id="template-help-teacher-profile"
a href="https://jamkazam.desk.com/customer/en/portal/articles/2405835-creating-your-teacher-profile#EditTeacherProfile" rel="external" Click here
| for a help article that explains how to fill out your teacher profile effectively to attract students.
+script type="text/template" id="template-help-jamtrack-variants"
+ .jamtrack-variants.big-dark-help
+ p
+ | The FULL package of a JamTrack will let you also download each track or mix to a personal device. You can always upgrade to FULL later.
+
+script type="text/template" id="template-help-jamtrack-upgrade"
+ .jamtrack-upgrade.big-dark-help
+ p
+ | Upgrade your JamTrack so that you can download each track or mix to a personal device.
script type="text/template" id="template-help-side-remaining-jamclass-credits"
.side-remaining-jamclass-credits
diff --git a/web/app/views/clients/_paypal_confirmation.html.slim b/web/app/views/clients/_paypal_confirmation.html.slim
new file mode 100644
index 000000000..1801ad234
--- /dev/null
+++ b/web/app/views/clients/_paypal_confirmation.html.slim
@@ -0,0 +1,8 @@
+.screen.secondary layout='screen' layout-id='paypal/confirm'
+ .content
+ .content-head
+ .content-icon=image_tag("content/icon_jamtracks.png", height: 19, width: 19)
+ h1 confirm payment
+ = render "screen_navigation"
+ .content-body
+ = react_component 'PayPalConfirmationScreen', {}
\ No newline at end of file
diff --git a/web/app/views/clients/_redeem_complete.html.slim b/web/app/views/clients/_redeem_complete.html.slim
index 3c2ba8ab5..1da8120c6 100644
--- a/web/app/views/clients/_redeem_complete.html.slim
+++ b/web/app/views/clients/_redeem_complete.html.slim
@@ -23,9 +23,13 @@ div layout="screen" layout-id="redeemComplete" id="redeemCompleteScreen" class="
| our free Mac or Windows app. This is the last step in the process, and you'll be ready to play.
| 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"
+ a.download-jamkazam-wrapper.jt-popup href="#" rel="external"
.download-jamkazam
- | Click Here to Get the Free JamKazam Application
+ | Click Here to Start Using Your JamTrack
+
+ a.download-jamkazam-wrapper href="/downloads" rel="external"
+ .just-some-div
+ | Do More With Your JamTrack - Click Here to Download Our Application
a.back-to-browsing href="/client#/jamtrack"
| or click here to browse more jamtracks
diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb
index 220bca602..b32625196 100644
--- a/web/app/views/clients/index.html.erb
+++ b/web/app/views/clients/index.html.erb
@@ -58,6 +58,7 @@
<%= render "jamtrack_search" %>
<%= render "jamtrack_filter" %>
<%= render "jamtrack_landing" %>
+<%= render "paypal_confirmation" %>
<%= render "shopping_cart" %>
<%= render "checkout_signin" %>
<%= render "checkout_payment" %>
diff --git a/web/app/views/clients/wizard/_sample_rate.html.slim b/web/app/views/clients/wizard/_sample_rate.html.slim
index 48ffd7341..f48ecf7d7 100644
--- a/web/app/views/clients/wizard/_sample_rate.html.slim
+++ b/web/app/views/clients/wizard/_sample_rate.html.slim
@@ -1,4 +1,5 @@
select.select-sample-rate
+ option value='USE_DEVICE_DEFAULT_SR' Match
option value='PREFER_44' 44100
option value='PREFER_48' 48000
option value='PREFER_96' 96000
\ No newline at end of file
diff --git a/web/app/views/dialogs/_recordingFinishedDialog.html.haml b/web/app/views/dialogs/_recordingFinishedDialog.html.haml
index 2fadd425c..17f324423 100644
--- a/web/app/views/dialogs/_recordingFinishedDialog.html.haml
+++ b/web/app/views/dialogs/_recordingFinishedDialog.html.haml
@@ -4,8 +4,11 @@
= image_tag "content/recordbutton-off.png", {:height => 20, :width => 20, :class => 'content-icon'}
%h1 recording finished
.dialog-inner
- Fill out the fields below and click the "SAVE" button to save this recording to your library. If you do not want to
- keep the recording, click the "DISCARD" button.
+ %span.nowait
+ Fill out the fields below and click the "SAVE" button to save this recording to your library. If you do not want to
+ keep the recording, click the "DISCARD" button.
+ %span.pleasewait
+ Please wait while we transfer the necessary audio files from your JamBlaster to your client...
%br/
%br/
%form.left.w40.mr20
@@ -36,7 +39,7 @@
%input{:checked => "checked", :name => "is_public", :type => "checkbox"}/
%label{:for => "is_public"} Public Recording
/ <
- .left.w50.ml30
+ .left.w50.ml30.preview-area
Preview Recording:
\#{render "clients/play_controls"}
diff --git a/web/app/views/dialogs/_shareDialog.html.erb b/web/app/views/dialogs/_shareDialog.html.erb
index 589e6b2d3..af94b48fe 100644
--- a/web/app/views/dialogs/_shareDialog.html.erb
+++ b/web/app/views/dialogs/_shareDialog.html.erb
@@ -38,7 +38,7 @@
diff --git a/web/app/views/landings/simple_jamclass.slim b/web/app/views/landings/simple_jamclass.slim
new file mode 100644
index 000000000..585e5bae7
--- /dev/null
+++ b/web/app/views/landings/simple_jamclass.slim
@@ -0,0 +1,5 @@
+- provide(:page_name, 'landing_page full simple_jamclass')
+- provide(:description, @description)
+- provide(:title, @title)
+
+= react_component 'SimpleJamClassPage', @page_data.to_json
\ No newline at end of file
diff --git a/web/app/views/landings/simple_jamtracks.slim b/web/app/views/landings/simple_jamtracks.slim
new file mode 100644
index 000000000..1e3ac5901
--- /dev/null
+++ b/web/app/views/landings/simple_jamtracks.slim
@@ -0,0 +1,5 @@
+- provide(:page_name, 'landing_page full simple_jamtracks')
+- provide(:description, @description)
+- provide(:title, @title)
+
+= react_component 'SimpleJamTracksPage', @page_data.to_json
\ No newline at end of file
diff --git a/web/app/views/layouts/web.html.erb b/web/app/views/layouts/web.html.erb
index b3c836504..e26147b4c 100644
--- a/web/app/views/layouts/web.html.erb
+++ b/web/app/views/layouts/web.html.erb
@@ -17,8 +17,11 @@
<%= render "layouts/social_meta" %>
<% end %>
<%= render "shared/ad_sense" %>
+ <% if @responsive %>
+
+ <% end %>
-
+
<%= javascript_include_tag "web/web" %>
@@ -53,7 +56,7 @@
<% if @show_cta_free_jamtrack %>
<%= link_to image_tag("web/free-jamtrack-cta.png", :alt => "ClICK HERE TO PICK YOUR FIRST JAMTRACK FREE!"), "/client#/jamtrack/search", class: "cta-free-jamtrack" %>
- $1.99 value
+ $4.99 value