session limits done

This commit is contained in:
Seth Call 2020-11-21 16:14:37 -06:00
parent 9da0b37aeb
commit ce301fd145
43 changed files with 1002 additions and 112 deletions

View File

@ -96,6 +96,7 @@ ALTER TABLE arses ADD COLUMN continent VARCHAR(200);
ALTER TABLE users ADD COLUMN recurly_subscription_id VARCHAR(100) DEFAULT NULL;
ALTER TABLE users ADD COLUMN recurly_token VARCHAR(200) DEFAULT NULL;
ALTER TABLE users ADD COLUMN recurly_subscription_state VARCHAR(20) DEFAULT NULL;
ALTER TABLE users ADD COLUMN subscription_plan_code VARCHAR(100) DEFAULT NULL;
CREATE TABLE subscriptions (
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
name VARCHAR(200) UNIQUE NOT NULL UNIQUE NOT NULL,
@ -113,3 +114,12 @@ CREATE TABLE subscriptions (
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
ALTER TABLE users ADD COLUMN subscription_trial_ends_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE users ADD COLUMN subscription_plan_reason varchar(20);
CREATE INDEX msuh_user_id ON music_sessions_user_history((1)) WHERE is_a_student;
-- alreday on WWW
CREATE INDEX msuh_user_id ON music_sessions_user_history USING btree (user_id);
CREATE INDEX msuh_created_at ON music_sessions_user_history USING btree (created_at);

View File

@ -54,6 +54,10 @@ message ClientMessage {
SCHEDULED_SESSION_RESCHEDULED = 180;
SCHEDULED_SESSION_REMINDER = 181;
SCHEDULED_SESSION_COMMENT = 182;
SESSION_KICK = 183;
// subscription-related
SUBSCRIPTION_CHANGED = 195;
// recording notifications
MUSICIAN_RECORDING_SAVED = 200;
@ -177,6 +181,10 @@ message ClientMessage {
optional ScheduledSessionRescheduled scheduled_session_rescheduled = 180;
optional ScheduledSessionReminder scheduled_session_reminder = 181;
optional ScheduledSessionComment scheduled_session_comment = 182;
optional SessionKick session_kick = 183;
// subscription-related
optional SubscriptionChanged subscription_changed = 195;
// recording notifications
optional MusicianRecordingSaved musician_recording_saved = 200;
@ -528,6 +536,15 @@ message ScheduledSessionComment {
optional string created_at = 8;
}
message SessionKick {
optional string session_id = 1;
optional string reason = 2;
}
message SubscriptionChanged {
}
message MusicianRecordingSaved {
optional string recording_id = 1;
optional string photo_url = 2;

View File

@ -54,6 +54,7 @@ require "jam_ruby/lib/em_helper"
require "jam_ruby/lib/nav"
require "jam_ruby/lib/html_sanitize"
require "jam_ruby/lib/guitar_center"
require "jam_ruby/subscription_definitions"
require "jam_ruby/resque/resque_jam_error"
require "jam_ruby/resque/resque_hooks"
require "jam_ruby/resque/audiomixer"

View File

@ -352,7 +352,7 @@ SQL
end
end
def update_session_controller(music_session_id)
def update_session_controller(music_session_id, kick_extras = false)
tracks_changed = false
active_music_session = ActiveMusicSession.find(music_session_id)
@ -362,7 +362,22 @@ SQL
# find next in line, because the current 'session controller' is not part of the session
tracks_changed = next_in_line(music_session, active_music_session)
end
if kick_extras
num_participants = active_music_session.users.count
puts("kick extras = num_participants #{num_participants}")
active_music_session.users.each do |user|
subscription_rules = user.subscription_rules(false)
puts "checking max players for #{user.email} #{subscription_rules[:max_players]}"
if subscription_rules[:max_players] && subscription_rules[:max_players] < num_participants
puts "kicking user #{user.email}"
end
end
end
end
tracks_changed
end
@ -436,7 +451,8 @@ SQL
if connection.errors.any?
raise ActiveRecord::Rollback
else
tracks_changed = update_session_controller(music_session.id)
tracks_changed = update_session_controller(music_session.id, kick_extras = true)
end
end

View File

@ -66,6 +66,7 @@ module ValidationMessages
CAN_ONLY_JOIN_SAME_SCHOOL_SESSION = "You can only join sessions from your school"
LICENSE_EXPIRED = "Your license has expired"
LICENSE_NOT_STARTED = "Your license has not started"
PLAN_PROHIBIT_MAX_PLAYERS = "The session size is greater than your plan allows"
# chat
CAN_ONLY_CHAT_SAME_SCHOOL = "You can only message others from your school"

View File

@ -936,6 +936,16 @@ module JamRuby
self.save
end
def play_time_remaining(user)
rules = SubscriptionDefinitions.rules(user.subscription_plan_code)
play_time_per_session = rules[:play_time_per_session]
if play_time_per_session.nil?
nil
else
(play_time_per_session * 3600) - MusicSessionUserHistory.where(music_session_id: self.id).where(user_id: user.id).sum("extract('epoch' from (COALESCE(session_removed_at, NOW()) - created_at))")
end
end
def self.sync(session_history)
music_session = MusicSession.find_by_id(session_history.id)

View File

@ -166,6 +166,18 @@ module JamRuby
errors.add(:music_session, ValidationMessages::LICENSE_NOT_STARTED)
end
num_participants = music_session.users.count
puts "NUM PARTICIPANTS BEFORE JOIN #{num_participants}"
subscription_rules = self.user.subscription_rules(dynamic_definitions = false)
max_players = subscription_rules[:max_players]
if !max_players.nil?
if num_participants >= max_players
errors.add(:music_session, ValidationMessages::PLAN_PROHIBIT_MAX_PLAYERS)
end
end
# unless user.admin?
# num_sessions = Connection.where(:user_id => user_id)
# .where(["(music_session_id IS NOT NULL) AND (aasm_state != ?)",EXPIRED_STATE.to_s])

View File

@ -274,7 +274,10 @@ module JamRuby
if sale.valid?
client = RecurlyClient.new
account = client.get_account(current_user)
account = client.find_or_create_account(current_user, nil, recurly_token)
client.update_billing_info_from_token(current_user, account, recurly_token)
if account.present?
recurly_response = client.create_subscription(current_user, plan_code, account)
@ -288,6 +291,7 @@ module JamRuby
sale.recurly_currency = recurly_response.currency
sale.save(validate: false)
else
puts "Could not find account to place order."
raise RecurlyClientError, "Could not find account to place order."
end
end

View File

@ -48,7 +48,7 @@ module JamRuby
def product
if product_type == JAMTRACK
JamTrack.find_by_id(product_id)
if product_type == SUBSCRIPTION
elsif product_type == SUBSCRIPTION
{name: product_id}
elsif product_type == GIFTCARD
GiftCardType.find_by_id(product_id)

View File

@ -2814,6 +2814,21 @@ module JamRuby
end
end
def subscription_rules(dynamic_definitions = true)
rules = SubscriptionDefinitions.rules(self.subscription_plan_code)
if dynamic_definitions
play_time_per_month = rules[:play_time_per_month]
if play_time_per_month.nil?
rules[:remaining_month_play_time] = nil
else
rules[:remaining_month_play_time] = (play_time_per_month * 3600) - MusicSessionUserHistory.where(user_id: self.id).where("date_trunc('month', created_at) = date_trunc('month', NOW())").where('session_removed_at IS NOT NULL').sum("extract('epoch' from (session_removed_at - created_at))")
end
end
rules
end
private
def create_remember_token
self.remember_token = SecureRandom.urlsafe_base64

View File

@ -11,9 +11,11 @@ module JamRuby
begin
#puts "Recurly.api_key: #{Recurly.api_key}"
account = Recurly::Account.create(options)
raise RecurlyClientError.new(account.errors) if account.errors.any?
if account.errors.any?
puts "Errors encountered while creating account: #{account.errors}"
raise RecurlyClientError.new(account.errors) if account.errors.any?
end
rescue Recurly::Error, NoMethodError => x
#puts "Error: #{x} : #{Kernel.caller}"
raise RecurlyClientError, x.to_s
else
if account
@ -30,7 +32,7 @@ module JamRuby
def delete_account(current_user)
account = get_account(current_user)
if (account)
if account
begin
account.destroy
rescue Recurly::Error, NoMethodError => x
@ -43,11 +45,26 @@ module JamRuby
end
def get_account(current_user)
current_user && current_user.recurly_code ? Recurly::Account.find(current_user.recurly_code) : nil
account = current_user && current_user.recurly_code ? Recurly::Account.find(current_user.recurly_code) : nil
# check again, assuming account_code is the user ID (can happen in error scenarios where we create the account
# on recurly, but couldn't save the account_code to the user.recurly_code field)
if !account
account = Recurly::Account.find(current_user.id)
# repair user local account info
if !account.nil?
current_user.update_attribute(:recurly_code, account.account_code)
end
end
account
rescue Recurly::Error => x
raise RecurlyClientError, x.to_s
end
def update_account(current_user, billing_info=nil)
account = get_account(current_user)
if(account.present?)
@ -94,9 +111,9 @@ module JamRuby
payments
end
def update_billing_info(current_user, billing_info=nil)
account = get_account(current_user)
if (account.present?)
def update_billing_info(current_user, billing_info=nil, account = nil)
account = get_account(current_user) if account.nil?
if account.present?
begin
account.billing_info = billing_info
account.billing_info.save
@ -111,6 +128,14 @@ module JamRuby
account
end
# token was created in the web ui. we can tell recurly to update the billing info on the account with just the token
def update_billing_info_from_token(current_user, account, recurly_token)
account.billing_info = {
token_id: recurly_token
}
account.billing_info.save!
end
def refund_user_subscription(current_user, jam_track)
jam_track_right=JamRuby::JamTrackRight.where("user_id=? AND jam_track_id=?", current_user.id, jam_track.id).first
if jam_track_right
@ -188,6 +213,7 @@ module JamRuby
# https://dev.recurly.com/docs/create-subscription
def create_subscription(user, plan_code, account)
puts "Creating subscription for #{user.email} with plan_code #{plan_code}"
subscription = Recurly::Subscription.create(
:plan_code => plan_code,
:currency => 'USD',
@ -200,14 +226,58 @@ module JamRuby
subscription
end
def find_subscription(user)
def find_subscription(user, account = nil)
subscription = nil
if user.recurly_subscription_id.nil?
nil
if account.nil?
account = get_account(user)
end
if account
account.subscriptions.find_each do |subscription|
puts "Subscription: #{subscription.inspect}"
end
subscription = account.subscriptions.first
else
puts "can't find subscription for account #{account}"
end
else
Recurly::Subscription.find(user.recurly_subscription_id)
subscription = Recurly::Subscription.find(user.recurly_subscription_id)
end
if user.recurly_subscription_id.nil?
puts "Repairing subscription ID on account"
user.update_attribute(:recurly_subscription_id, subscription.id)
user.recurly_subscription_id = subscription.id
end
subscription
end
def change_subscription_plan(current_user, plan_code)
subscription = find_subscription(current_user)
if subscription.nil?
puts "no subscription found for user #{current_user.email}"
return false
end
puts "subscription.plan #{subscription.plan}"
if subscription.plan.plan_code == plan_code
puts "plan code was the same as requested: #{plan_code}"
return false
end
result = subscription.update_attributes(
:plan_code => plan_code,
:timeframe => 'bill_date'
)
puts "change subscription plan #{result}"
return result
end
def sync_subscription(user)
subscription = find_subscription(user)
@ -225,13 +295,17 @@ module JamRuby
end
end
def find_or_create_account(current_user, billing_info)
def find_or_create_account(current_user, billing_info, recurly_token = nil)
account = get_account(current_user)
if(account.nil?)
if !account
account = create_account(current_user, billing_info)
else
update_billing_info(current_user, billing_info)
elsif !billing_info.nil?
update_billing_info(current_user, billing_info, account)
end
if !recurly_token.nil?
update_billing_info_from_token(current_user, account, recurly_token)
end
account
end

View File

@ -0,0 +1,76 @@
module JamRuby
class SubscriptionDefinitions
JAM_SILVER = 'jamsubsilver'
JAM_SILVER_WITH_TRIAL = 'jamsubsilvertrial'
JAM_GOLD = 'jamsubgold'
JAM_GOLD_WITH_TRIAL = 'jamsubgoldtrial'
JAM_PLATINUM = 'jamsubplatinum'
JAM_PLATINUM_WITH_TRIAL = 'jamsubplatinumtrial'
# ALL IN HOURS
FREE_PLAY_TIME_PER_SESSION = 1
FREE_PLAY_TIME_PER_MONTH = 4
SILVER_PLAY_TIME_PER_SESSION = nil # unlimited
SILVER_PLAY_TIME_PER_MONTH = 10
GOLD_PLAY_TIME_PER_SESSION = nil # unlimited
GOLD_PLAY_TIME_PER_MONTH = nil # unlimited
PLATINUM_PLAY_TIME_PER_SESSION = nil # unlimited
PLATINUM_PLAY_TIME_PER_MONTH = nil # unlimited
FREE_PLAN = {
play_time_per_month: FREE_PLAY_TIME_PER_MONTH,
play_time_per_session: FREE_PLAY_TIME_PER_SESSION,
recording: false,
video: 'no',
audio_bitrate: '128',
broadcasting: 'no',
max_players: 4
}
SILVER_PLAN = {
play_time_per_month: SILVER_PLAY_TIME_PER_MONTH,
play_time_per_session: SILVER_PLAY_TIME_PER_SESSION,
recording: false,
video: 'cif',
audio_bitrate: '192',
broadcasting: 'free',
max_players: 6
}
GOLD_PLAN = {
play_time_per_month: GOLD_PLAY_TIME_PER_MONTH,
play_time_per_session: GOLD_PLAY_TIME_PER_SESSION,
recording: true,
video: '720p',
audio_bitrate: '256',
broadcasting: 'free',
max_players: nil
}
PLATINUM_PLAN = {
play_time_per_month: PLATINUM_PLAY_TIME_PER_MONTH,
play_time_per_session: PLATINUM_PLAY_TIME_PER_SESSION,
recording: true,
video: '1080p',
audio_bitrate: '512',
broadcasting: 'busking',
max_players: nil
}
def self.rules(plan_code)
if plan_code == nil
FREE_PLAN
elsif plan_code == JAM_SILVER || plan_code == JAM_SILVER_WITH_TRIAL
SILVER_PLAN
elsif plan_code == JAM_GOLD || plan_code == JAM_GOLD_WITH_TRIAL
GOLD_PLAN
elsif plan_code == JAM_PLATINUM || plan_code == JAM_PLATINUM_WITH_TRIAL
PLATINUM_PLAN
else
raise "unknown plan #{plan_code}"
end
end
end
end

View File

@ -105,7 +105,7 @@ gem 'rubyzip'
gem 'slim'
gem 'htmlentities'
gem 'sanitize'
gem 'recurly'
gem 'recurly', '~> 2'
#gem 'guard', '2.7.3'
#gem 'influxdb' #, '0.1.8'
gem 'cause' # needed by influxdb
@ -167,7 +167,7 @@ end
gem 'sass-rails'
gem 'coffee-rails'
gem 'uglifier'
gem 'coffee-script-source', '1.11.1'
gem 'coffee-script-source', '1.12.2'
group :test, :cucumber do
gem 'simplecov', '~> 0.7.1'
gem 'simplecov-rcov'

View File

@ -137,7 +137,7 @@ GEM
coffee-script (2.4.1)
coffee-script-source
execjs
coffee-script-source (1.11.1)
coffee-script-source (1.12.2)
concurrent-ruby (1.0.5)
connection_pool (2.2.1)
crass (1.0.2)
@ -577,7 +577,7 @@ GEM
execjs
rails (>= 3.2)
tilt
recurly (2.10.0)
recurly (2.18.16)
redis (3.3.3)
redis-namespace (1.5.3)
redis (~> 3.0, >= 3.0.4)
@ -764,7 +764,7 @@ DEPENDENCIES
carrierwave_direct
cause
coffee-rails
coffee-script-source (= 1.11.1)
coffee-script-source (= 1.12.2)
database_cleaner (= 1.3.0)
devise (= 3.3.0)
em-websocket (>= 0.4.0)
@ -826,7 +826,7 @@ DEPENDENCIES
rails-observers
railties (> 4.2)
react-rails (= 1.3.3)
recurly
recurly (~> 2)
responders (~> 2.0)
resque
resque-dynamic-queues

View File

@ -175,11 +175,17 @@
}
else {
$btn.click(function() {
var hideOnClick = true
if (button.click) {
button.click();
var result = button.click();
if(result === 'noclose') {
hideOnClick = false
}
}
hide();
if(hideOnClick) {
hide();
}
return false;
});
}

View File

@ -182,6 +182,14 @@
videoShared = true;
}
function IsVstLoaded() {
return false;
}
function hasVstAssignment() {
return false;
}
function FTUEGetInputLatency() {
dbg("FTUEGetInputLatency");
return 2;
@ -793,6 +801,10 @@
logger.debug("Fake JamClient: SessionAudioResync()");
}
function getConnectionDetail(arg1, arg2) {
return {}
}
function SessionGetAllControlState(isMasterOrPersonal) {
var mixerIds = SessionGetIDs()
return SessionGetControlState(mixerIds, isMasterOrPersonal);
@ -1651,6 +1663,7 @@
this.SessionSetMasterLocalMix = SessionSetMasterLocalMix;
this.SessionGetDeviceLatency = SessionGetDeviceLatency;
this.SessionAudioResync = SessionAudioResync;
this.getConnectionDetail = getConnectionDetail;
// Track
this.TrackGetChannels = TrackGetChannels;
@ -1751,7 +1764,10 @@
this.FTUEGetVideoShareEnable = FTUEGetVideoShareEnable;
this.isSessVideoShared = isSessVideoShared;
this.SessStopVideoSharing = SessStopVideoSharing;
this.SessStartVideoSharing = SessStartVideoSharing;
this.SessStartVideoSharing = SessStartVideoSharing
this.IsVstLoaded = IsVstLoaded;
this.hasVstAssignment = hasVstAssignment;
// Clipboard
this.SaveToClipboard = SaveToClipboard;

View File

@ -2802,6 +2802,48 @@
})
}
function createSubscription(options) {
options = options || {}
return $.ajax({
type: "POST",
url: '/api/recurly/create_subscription',
dataType: "json",
contentType: 'application/json',
data: JSON.stringify(options)
})
}
function getSubscription() {
return $.ajax({
type: "GET",
url: '/api/recurly/get_subscription',
dataType: "json",
contentType: 'application/json'
})
}
function changeSubscription(options) {
options = options || {}
return $.ajax({
type: "POST",
url: '/api/recurly/change_subscription',
dataType: "json",
contentType: 'application/json',
data: JSON.stringify(options)
})
}
function cancelSubscription(options) {
options = options || {}
return $.ajax({
type: "POST",
url: '/api/recurly/cancel_subscription',
dataType: "json",
contentType: 'application/json',
data: JSON.stringify(options)
})
}
function createLiveStream(musicSessionId) {
return $.ajax({
type: "POST",
@ -3128,6 +3170,10 @@
this.findFriendSessions = findFriendSessions;
this.findPublicSessions = findPublicSessions;
this.getConfigClient = getConfigClient;
this.createSubscription = createSubscription;
this.getSubscription = getSubscription;
this.changeSubscription = changeSubscription;
this.cancelSubscription= cancelSubscription;
return this;
};
})(window, jQuery);

View File

@ -735,6 +735,34 @@
//decrementNotificationCount();
}
function registerSessionKick() {
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SESSION_KICK, function(header, payload) {
logger.debug("Handling SESSION_KICK msg " + JSON.stringify(payload));
context.SessionAction.leaveSession({
location: '/client#/home',
})
var buttons = []
buttons.push({name: 'CLOSE', buttonStyle: 'button-grey'})
buttons.push({name: 'COMPARE PLANS', buttonStyle: 'button-grey', click: function() {
context.JK.popExternalLink("https://jamkazam.freshdesk.com/support/solutions/articles/66000122535-what-are-jamkazam-s-free-vs-premium-features-")
return 'noclose'
}})
buttons.push({
name: 'UPGRADE PLAN',
buttonStyle: 'button-orange',
click: function() {
context.JK.popExternalLink("/client#/account/subscription", true)
}
})
context.JK.Banner.show({
title: "Session Too Big For Current Plan",
html: context._.template($('#template-session-too-big-kicked').html(), {}, { variable: 'data' }),
buttons: buttons});
})
}
function registerSessionInvitation() {
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SESSION_INVITATION, function(header, payload) {
logger.debug("Handling SESSION_INVITATION msg " + JSON.stringify(payload));

View File

@ -4,6 +4,8 @@ logger = context.JK.logger
AppStore = context.AppStore
UserStore = context.UserStore
SubscriptionStore = context.SubscriptionStore
SubscriptionActions = context.SubscriptionActions
profileUtils = context.JK.ProfileUtils
@ -12,7 +14,8 @@ profileUtils = context.JK.ProfileUtils
mixins: [
ICheckMixin,
Reflux.listenTo(AppStore, "onAppInit"),
Reflux.listenTo(UserStore, "onUserChanged")
Reflux.listenTo(UserStore, "onUserChanged"),
Reflux.listenTo(SubscriptionStore, "onSubscriptionChanged")
]
onAppInit: (@app) ->
@ -21,26 +24,22 @@ profileUtils = context.JK.ProfileUtils
onUserChanged: (userState) ->
@setState({user: userState?.user})
componentDidUpdate: () ->
console.log("did update")
onSubscriptionChanged: (subscription) ->
@setState({subscription: subscription})
beforeHide: (e) ->
@screenVisible = false
return true
beforeShow: (e) ->
console.log("before show")
SubscriptionActions.updateSubscription()
afterShow: (e) ->
@screenVisible = true
logger.debug("AccountSubscriptionScreen: afterShow")
getInitialState: () ->
{
user: null,
updating: false
}
{ user: null, updating: false, subscription: null}
onCancel: (e) ->
e.preventDefault()
@ -48,15 +47,31 @@ profileUtils = context.JK.ProfileUtils
render: () ->
if @state.subscription
if @state.subscription.plan
currentSubscription = `<CurrentSubscription subscription={this.state.subscription} />`
createSubscription = `<Subscription subscription={this.state.subscription}/>`
content = `<div>
<div className="current-subscription-block">
{currentSubscription}
</div>
<div className="payment-block">
{createSubscription}
</div>
</div>`
else
content = `<div className="loading">Loading...</div>`
`<div className="content-body-scroller">
<div className="profile-header profile-head">
<div className="store-header">subscription:</div>
</div>
</div>
<div className="profile-body">
<div className="profile-wrapper">
<div className="main-content">
<Subscription />
{content}
<div className="actions">
<a onClick={this.onCancel}>LEAVE</a>
</div>

View File

@ -0,0 +1,99 @@
context = window
rest = context.JK.Rest()
logger = context.JK.logger
LocationActions = context.LocationActions
SubscriptionActions = context.SubscriptionActions
UserStore = context.UserStore
AppStore = context.AppStore
@CurrentSubscription = React.createClass({
mixins: [Reflux.listenTo(AppStore, "onAppInit")]
getInitialState: () ->
{
selectedPlan: null
}
getDisplayNameTier: (plan_code) ->
for subscriptionCode in gon.global.subscription_codes
if plan_code == subscriptionCode.id
return subscriptionCode.name
return "Unknown plan code=#{plan_code}"
getDisplayNamePrice: (plan_code) ->
for subscriptionCode in gon.global.subscription_codes
if plan_code == subscriptionCode.id
return subscriptionCode.price
return "Unknown plan code=#{plan_code}"
onPlanChanged: (e) ->
val = $(e.target).val()
@setState({selectedPlan: val})
currentPlan: () ->
this.state.selectedPlan || this.props.selectedPlan || ''
onChangeSubmit: (event) ->
form = event.target
event.preventDefault()
if !@state.selectedPlan
return
SubscriptionActions.changeSubscription(this.state.selectedPlan)
onCancelPlan: (event) ->
if confirm("Are you sure you want to cancel your plan?")
SubscriptionActions.cancelSubscription()
componentDidMount: () ->
@root = $(@getDOMNode())
document.querySelector('#change-subscription-form').addEventListener('submit', @onChangeSubmit.bind(this))
render: () ->
plan_codes = []
for plan in gon.global.subscription_codes
plan_codes.push(`<option key={plan.id} value={plan.id}>{plan.name} ({plan.price}/month)</option>`)
plansJsx = `
<select name="plan_code" onChange={this.onPlanChanged} value={this.currentPlan()} >{plan_codes}</select>`
changeClass = 'button-orange'
if !@state.selectedPlan
changeClass = changeClass + ' disabled'
if @props.subscription.pending_subscription
currentPlan = this.getDisplayNameTier(this.props.subscription.pending_subscription.plan.plan_code)
billingAddendum = `<span>(billed at {this.getDisplayNameTier(this.props.subscription.plan.plan_code)} until next billing cycle</span>`
else
currentPlan = this.getDisplayNameTier(this.props.subscription.plan.plan_code)
billingAddendum = null
`<div>
<div>
<h3>Current Subscription</h3>
<div>
{currentPlan}
{billingAddendum}
</div>
</div>
<div>
<h3>Change Plan</h3>
<form id="change-subscription-form">
<label for="plan_code">Change Plan To:</label>
{plansJsx}
<button className={changeClass}>CHANGE PLAN</button>
</form>
</div>
<div>
<h3>Cancel Plan</h3>
<button className="button-orange" onClick={this.onCancelPlan}>CANCEL PLAN</button>
</div>
</div>`
})

View File

@ -13,11 +13,13 @@ UserStore = context.UserStore
getInitialState: () ->
{
clicked: false,
selectedCountry: null
selectedCountry: null,
selectedPlan: null,
subscription: null
}
onLocationsChanged: (countries) ->
console.log("countires in ", countries)
console.log("countries in ", countries)
@setState({countries: countries})
onCountryChanged: (e) ->
@ -27,18 +29,26 @@ UserStore = context.UserStore
currentCountry: () ->
this.state.selectedCountry || this.props.selectedCountry || ''
onPlanChanged: (e) ->
val = $(e.target).val()
@setState({selectedPlan: val})
currentPlan: () ->
this.state.selectedPlan || this.props.selectedPlan || ''
openBrowser: () ->
context.JK.popExternalLink("https://www.jamkazam.com/client#/subscription")
onRecurlyToken: (err, token) ->
console.log("TOKEN", token)
onRecurlyToken: (err, token_data) ->
if err
console.log("error", err)
# handle error using err.code and err.fields
else
# recurly.js has filled in the 'token' field, so now we can submit the
# form to your server
console.log("eintercepted")
console.log("eintercepted", token_data)
rest.createSubscription({plan_code: @state.selectedPlan, recurly_token: token_data.id})
onFormSubmit: (event) ->
form = event.target
@ -51,6 +61,7 @@ UserStore = context.UserStore
context.recurly.configure(gon.global.recurly_public_api_key)
window.configuredRecurly = true
componentDidMount: () ->
LocationActions.load()
@ -100,7 +111,16 @@ UserStore = context.UserStore
countryJsx = `
<select name="countries" onChange={this.onCountryChanged} value={this.currentCountry()} data-recurly="country" autocomplete="shipping country">{countries}</select>`
`<form id="subscription-form">
plan_codes = [`<option key='' value='' >Select Plan</option>`]
for plan in gon.global.subscription_codes
plan_codes.push(`<option key={plan.id} value={plan.id}>{plan.name} ({plan.price}/month)</option>`)
plansJsx = `
<select name="plan_code" onChange={this.onPlanChanged} value={this.currentPlan()} >{plan_codes}</select>`
`<div>
<h3>Update Payment</h3>
<form id="subscription-form">
<div id="subscription-account-data">
<label for="first_name">First Name:</label>
<input type="text" data-recurly="first_name" required autocomplete="cc-give-name"></input>
@ -125,14 +145,16 @@ UserStore = context.UserStore
<label for="country">Country:</label>
{countryJsx}
<label for="plan_code">Plan:</label>
{plansJsx}
</div>
<div id="subscription-elements">
</div>
<input type="hidden" name="recurly-token" data-recurly="token"></input>
<button>submit</button>
</form>`
<button className="button-orange">SUBMIT</button>
</form>
</div>`
})

View File

@ -0,0 +1,83 @@
context = window
@SubscriptionConcern = React.createClass({
displayName: 'SubscriptionConcern'
getTimeRemaining: (t) ->
if t < 0
t = -t
seconds = Math.floor( (t/1000) % 60 );
minutes = Math.floor( (t/1000/60) % 60 );
hours = Math.floor( (t/(1000*60*60)) % 24 );
days = Math.floor( t/(1000*60*60*24) );
return {
'total': t,
'days': days,
'hours': hours,
'minutes': minutes,
'seconds': seconds
};
displayTime: () ->
if false
# offset time by 10 minutes to get the 'you need to wait message' in
untilTime = @getTimeRemaining(@props.subscription.until.total + (10 * 60 * 1000))
else
untilTime = @props.subscription.until
timeString = ''
if untilTime.days != 0
timeString += "#{untilTime.days} days, "
if untilTime.hours != 0 || timeString.length > 0
timeString += "#{untilTime.hours} hours, "
if untilTime.minutes != 0 || timeString.length > 0
timeString += "#{untilTime.minutes} minutes, "
if untilTime.seconds != 0 || timeString.length > 0
timeString += "#{untilTime.seconds} seconds"
if timeString == ''
'now!'
timeString
openBrowserToPayment: (e) ->
context.JK.popExternalLink("/client#/account/subscription", true)
e.stopPropagation();
openBrowserToPlanComparison: (e) ->
context.JK.popExternalLink("https://jamkazam.freshdesk.com/support/solutions/articles/66000122535-what-are-jamkazam-s-free-vs-premium-features-")
e.stopPropagation();
return 'noclose'
render: () ->
content = null
if @props.subscription.until.total < 2000
content = `<div className="message">
<p>You have run out of time.</p>
<p>You can <a href="/client#/account/subscription" onClick={this.openBrowserToPayment}>upgrade your plan</a> to continue playing.</p>
</div>`
else
if @props.subscription.main_concern_type == 'remaining_session_play_time'
content = `<div className="message">
<p>You will run out play time for this session in:</p>
<p className="time">{this.displayTime()}</p>
<p>You can <a href="/client#/account/subscription" onClick={this.openBrowserToPayment}>upgrade your plan</a> to continue playing.</p>
</div>`
else
content = `<div className="message">
<p>You will run out of monthly play time in:</p>
<p className="time">{this.displayTime()}</p>
<p>You can <a href="/client#/account/subscription" onClick={this.openBrowserToPayment}>upgrade your plan</a> to continue playing.</p>
</div>`
if content?
`<div className="broadcast-notification subscription">
{content}
</div>`
else
`<div></div>`
})

View File

@ -6,14 +6,13 @@ ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
@TopMessageHolder = React.createClass(
{
displayName: 'Top Message Holder',
mixins: [Reflux.listenTo(ConfigStore, "onConfig")]
minimum_time_until_sub_prompt: 1000 * 60 * 30 # 30 minutes
mixins: [Reflux.listenTo(ConfigStore, "onConfig"), Reflux.connect(context.JK.Stores.Broadcast, 'notification')]
getInitialState: () ->
{}
onConfig: (configs) ->
if configs.top_message
@setState({top_message: configs.top_message})
@ -23,7 +22,12 @@ ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
return false
render: () ->
if @state.top_message
# only show the subscription concern message if there is a concerpn and due time is less thant 30 minutes away
if @state.notification && @state.notification.subscriptionConcern? && @state.notification.subscriptionConcern.until.total < @minimum_time_until_sub_prompt
`<div id="broadcast-notification-holder" className="broadcast-notification-holder" >
<SubscriptionConcern key="subscriptionconcern" subscription={this.state.notification.subscriptionConcern} />
</div>`
else if @state.top_message
`<div id="broadcast-notification-holder" className="broadcast-notification-holder" >
<div className="broadcast-notification config" dangerouslySetInnerHTML={{__html:this.state.top_message}}>

View File

@ -0,0 +1,8 @@
context = window
@SubscriptionActions = Reflux.createActions({
updateSubscription: {}
changeSubscription: {}
cancelSubscription: {}
updatePayment: {}
})

View File

@ -2,7 +2,7 @@ context = window
@SessionHelper = class SessionHelper
constructor: (app, session, participantsEverSeen, isRecording, downloadingJamTrack, preppingVstEnable) ->
constructor: (app, session, participantsEverSeen, isRecording, downloadingJamTrack, preppingVstEnable, sessionRules, subscriptionRules) ->
@app = app
@session = session
@participantsEverSeen = participantsEverSeen
@ -10,6 +10,8 @@ context = window
@downloadingJamTrack = downloadingJamTrack
@preppingVstEnable = preppingVstEnable
@isLesson = @session?.lesson_session?
@sessionRules = sessionRules
@subscriptionRules = subscriptionRules
if @isLesson
@lessonId = @session.lesson_session.id

View File

@ -2,6 +2,7 @@ $ = jQuery
context = window
logger = context.JK.logger
broadcastActions = @BroadcastActions
SessionActions = @SessionActions
rest = context.JK.Rest()
@ -17,6 +18,10 @@ BroadcastStore = Reflux.createStore(
currentLessonTimer: null
teacherFault: false
isJamClass: false
sessionRules: null
subscriptionRules: null
subscriptionConcern: null
currentSubscriptionTimer: null
init: ->
this.listenTo(context.AppStore, this.onAppInit)
this.listenTo(context.SessionStore, this.onSessionChange)
@ -29,6 +34,45 @@ BroadcastStore = Reflux.createStore(
@timeManagement()
@changed()
subscriptionTick: () ->
@subscriptionManagement()
@changed()
openBrowserToPayment: () ->
context.JK.popExternalLink("/client#/account/subscription", true)
openBrowserToPlanComparison: () ->
context.JK.popExternalLink("https://jamkazam.freshdesk.com/support/solutions/articles/66000122535-what-are-jamkazam-s-free-vs-premium-features-")
return 'noclose'
subscriptionManagement: () ->
@subscriptionConcern.until = @lessonUtils.getTimeRemaining(@subscriptionConcern.main_concern_time)
if @subscriptionConcern.until.total < -15000
leaveBehavior =
location: "/client#/findSession"
buttons = []
buttons.push({name: 'CLOSE', buttonStyle: 'button-grey'})
buttons.push({name: 'COMPARE PLANS', buttonStyle: 'button-grey', click: (() => (@openBrowserToPlanComparison()))})
buttons.push({
name: 'UPGRADE PLAN',
buttonStyle: 'button-orange',
click: (() => (@openBrowserToPayment()))
})
if @subscriptionConcern.main_concern_type == "remaining_month_play_time"
context.JK.Banner.show({
title: "Out of Time For This Month",
html: context._.template($('#template-no-remaining-month-play-time').html(), {}, { variable: 'data' }),
buttons: buttons});
else
context.JK.Banner.show({
title: "Out of Time For This Session",
html: context._.template($('#template-no-remaining-session-play-time').html(), {}, { variable: 'data' }),
buttons: buttons});
SessionActions.leaveSession.trigger(leaveBehavior)
timeManagement: () ->
lastCheck = $.extend({}, @currentLesson)
lessonSession = @currentLesson
@ -61,7 +105,7 @@ BroadcastStore = Reflux.createStore(
logger.debug("BroadcastStore: lesson is over")
@currentLesson.completed = true
@currentLesson.success = @analysis.success
@clearTimer()
@clearLessonTimer()
@changed()
else if @analysis.analysis.reason != 'teacher_fault'
logger.debug("BroadcastStore: not teacher fault; clearing lesson info")
@ -69,21 +113,31 @@ BroadcastStore = Reflux.createStore(
else
logger.debug("BroadcastStore: teacher is at fault")
@teacherFault = true
@clearTimer()
@clearLessonTimer()
@changed()
clearLesson: () ->
if @currentLesson?
@currentLesson = null
@clearTimer()
@clearLessonTimer()
@teacherFault = false
@changed()
clearTimer: () ->
clearSubscription: () ->
@subscriptionConcern = null
@clearSubscriptionTimer()
@changed()
clearLessonTimer: () ->
if @currentLessonTimer?
clearInterval(@currentLessonTimer)
@currentLessonTimer = null
clearSubscriptionTimer: () ->
if @currentSubscriptionTimer?
clearInterval(@currentSubscriptionTimer)
@currentSubscriptionTimer = null
onNavChange: (nav) ->
path = nav.screenPath.toLowerCase()
@isJamClass = path.indexOf('jamclass') > -1 || path.indexOf('teacher') > -1
@ -93,29 +147,59 @@ BroadcastStore = Reflux.createStore(
@session = session
currentSession = session.session
if currentSession? && currentSession.lesson_session? && session.inSession()
if currentSession? && session.inSession()
@subscriptionRules = session.subscriptionRules
@sessionRules = session.sessionRules
@currentSession = currentSession
if @subscriptionRules
lessonSession = currentSession.lesson_session
# so that receivers can check type of info coming at them via one-way events
lessonSession.isLesson = true
if !@subscriptionRules.remaining_month_until? && !@sessionRules.remaining_session_until?
console.log("no license issues")
@subscriptionConcern = null
else
@subscriptionConcern = {}
if !@subscriptionRules.remaining_month_until?
@subscriptionConcern.main_concern_time = @sessionRules.remaining_session_until
@subscriptionConcern.main_concern_type = "remaining_session_play_time"
else if !@sessionRules.remaining_session_play_time?
@subscriptionConcern.main_concern_time = @subscriptionRules.remaining_month_until
@subscriptionConcern.main_concern_type = "remaining_month_play_time"
else
if @sessionRules.remaining_session_play_time < @subscriptionRules.remaining_month_play_time
@subscriptionConcern.main_concern_time = @sessionRules.remaining_session_until
@subscriptionConcern.main_concern_type = "remaining_session_play_time"
else
@subscriptionConcern.main_concern_time = @subscriptionRules.remaining_month_until
@subscriptionConcern.main_concern_type = "remaining_month_play_time"
if lessonSession.status == 'completed'
lessonSession.completed = true
lessonSession.success = lessonSession.success
#else
# rest.getLessonAnalysis({id: lessonSession.id}).done((response) => @lessonAnalysisDone(response)).fail(@app.ajaxError)
@subscriptionManagement()
#logger.debug("BroadcastStore: session can play until: ", @subscriptionConcern.until, @subscriptionConcern.main_concert_time)
if !@currentSubscriptionTimer?
@currentSubscriptionTimer= setInterval((() => @subscriptionTick()), 1000)
@changed()
@currentLesson = lessonSession
@timeManagement()
logger.debug("BroadcastStore: currentLesson until: ", @currentLesson.until, lessonSession.scheduled_start)
if !@currentLessonTimer?
@currentLessonTimer = setInterval((() => @lessonTick()), 1000)
@changed()
if currentSession.lesson_session?
@currentSession = currentSession
lessonSession = currentSession.lesson_session
# so that receivers can check type of info coming at them via one-way events
lessonSession.isLesson = true
if lessonSession.status == 'completed'
lessonSession.completed = true
lessonSession.success = lessonSession.success
#else
# rest.getLessonAnalysis({id: lessonSession.id}).done((response) => @lessonAnalysisDone(response)).fail(@app.ajaxError)
@currentLesson = lessonSession
@timeManagement()
logger.debug("BroadcastStore: currentLesson until: ", @currentLesson.until, lessonSession.scheduled_start)
if !@currentLessonTimer?
@currentLessonTimer = setInterval((() => @lessonTick()), 1000)
@changed()
else
@clearLesson()
@clearSubscription()
onLoad: () ->
logger.debug("loading broadcast notification...")
@ -136,21 +220,10 @@ BroadcastStore = Reflux.createStore(
@changed()
changed: () ->
if @currentLesson?
@currentLesson.isStudent == @currentLesson.student_id == context.JK.currentUserId
@currentLesson.isTeacher = !@currentLesson.isStudent
@currentLesson.teacherFault = @teacherFault
@currentLesson.teacherPresent = @session.findParticipantByUserId(@currentLesson.teacher_id)
@currentLesson.studentPresent = @session.findParticipantByUserId(@currentLesson.student_id)
if (@currentLesson.teacherPresent? && @currentLesson.isStudent) || (@currentLesson.studentPresent? && @currentLesson.isTeacher)
# don't show anything if the other person is there
this.trigger(null)
else
this.trigger(@currentLesson)
else if @isJamClass
this.trigger({isJamClass: true})
if @subscriptionConcern?
this.trigger({subscriptionConcern: @subscriptionConcern})
else
this.trigger(@broadcast)
this.trigger(null)
}
)

View File

@ -21,6 +21,8 @@ ConfigureTracksActions = @ConfigureTracksActions
currentSessionId: null
currentSession: null
currentOrLastSession: null
sessionRules: null
subscriptionRules: null
startTime: null
currentParticipants: {}
participantsEverSeen: {}
@ -53,7 +55,7 @@ ConfigureTracksActions = @ConfigureTracksActions
@sessionUtils = context.JK.SessionUtils
@recordingModel = new context.JK.RecordingModel(@app, rest, context.jamClient);
RecordingActions.initModel(@recordingModel)
@helper = new context.SessionHelper(@app, @currentSession, @participantsEverSeen, @isRecording, @downloadingJamTrack, @enableVstTimeout?)
@helper = new context.SessionHelper(@app, @currentSession, @participantsEverSeen, @isRecording, @downloadingJamTrack, @enableVstTimeout?, @sessionRules, @subscriptionRules)
onSessionJoinedByOther: (payload) ->
clientId = payload.client_id
@ -78,7 +80,7 @@ ConfigureTracksActions = @ConfigureTracksActions
onVideoChanged: (@videoState) ->
issueChange: () ->
@helper = new context.SessionHelper(@app, @currentSession, @participantsEverSeen, @isRecording, @downloadingJamTrack, @enableVstTimeout?)
@helper = new context.SessionHelper(@app, @currentSession, @participantsEverSeen, @isRecording, @downloadingJamTrack, @enableVstTimeout?, @sessionRules, @subscriptionRules)
this.trigger(@helper)
onWindowBackgrounded: () ->
@ -726,6 +728,13 @@ ConfigureTracksActions = @ConfigureTracksActions
deferred.resolve();
deferred
openBrowserToPayment: () ->
context.JK.popExternalLink("/client#/account/subscription", true)
openBrowserToPlanComparison: () ->
context.JK.popExternalLink("https://jamkazam.freshdesk.com/support/solutions/articles/66000122535-what-are-jamkazam-s-free-vs-premium-features-")
return 'noclose'
joinSession: () ->
context.jamClient.SessionRegisterCallback("JK.HandleBridgeCallback2");
context.jamClient.RegisterRecordingCallbacks("JK.HandleRecordingStartResult", "JK.HandleRecordingStopResult", "JK.HandleRecordingStarted", "JK.HandleRecordingStopped", "JK.HandleRecordingAborted");
@ -816,13 +825,44 @@ ConfigureTracksActions = @ConfigureTracksActions
@app.notifyAlert("No Inputs Configured", $('<span>You will need to reconfigure your audio device.</span>'))
else if response["errors"] && response["errors"]["music_session"] && response["errors"]["music_session"][0] == ["is currently recording"]
leaveBehavior =
location: "/client#/findSession"
notify:
title: "Unable to Join Session"
text: "The session is currently recording."
SessionActions.leaveSession.trigger(leaveBehavior)
else if response["errors"] && response["errors"]["remaining_session_play_time"]
leaveBehavior =
location: "/client#/findSession"
buttons = []
buttons.push({name: 'CLOSE', buttonStyle: 'button-grey'})
buttons.push({name: 'COMPARE PLANS', buttonStyle: 'button-grey', click: (() => (@openBrowserToPlanComparison()))})
buttons.push({
name: 'UPGRADE PLAN',
buttonStyle: 'button-orange',
click: (() => (@openBrowserToPayment()))
})
context.JK.Banner.show({
title: "Out of Time For This Session",
html: context._.template($('#template-no-remaining-session-play-time').html(), {}, { variable: 'data' }),
buttons: buttons});
SessionActions.leaveSession.trigger(leaveBehavior)
else if response["errors"] && response["errors"]["remaining_month_play_time"]
leaveBehavior =
location: "/client#/findSession"
buttons = []
buttons.push({name: 'CLOSE', buttonStyle: 'button-grey'})
buttons.push({name: 'COMPARE PLANS', buttonStyle: 'button-grey', click: (() => (@openBrowserToPlanComparison()))})
buttons.push({
name: 'UPGRADE PLAN',
buttonStyle: 'button-orange',
click: (() => (@openBrowserToPayment()))
})
context.JK.Banner.show({
title: "Out of Time for the Month",
html: context._.template($('#template-no-remaining-month-play-time').html(), {}, { variable: 'data' }),
buttons: buttons});
SessionActions.leaveSession.trigger(leaveBehavior)
else
@app.notifyServerError(xhr, 'Unable to Join Session');
else
@ -1051,6 +1091,30 @@ ConfigureTracksActions = @ConfigureTracksActions
if sessionData != null
@currentOrLastSession = sessionData
if sessionData.session_rules
@sessionRules = sessionData.session_rules
# TESTING:
@sessionRules.remaining_session_play_time = 60 * 30 + 15 # 30 minutes and 15 seconds
# compute timestamp due time
if @sessionRules.remaining_session_play_time?
until_time = new Date()
until_time = new Date(until_time.getTime() + @sessionRules.remaining_session_play_time * 1000)
@sessionRules.remaining_session_until = until_time
if sessionData.subscription_rules
@subscriptionRules = sessionData.subscription_rules
# TESTING:
#@subscriptionRules.remaining_month_play_time = 60 * 30 + 15 # 30 minutes and 15 seconds
if @subscriptionRules.remaining_month_play_time?
until_time = new Date()
until_time = new Date(until_time.getTime() + @subscriptionRules.remaining_month_play_time * 1000)
#until_time.setSeconds(until_time.getSeconds() + @subscriptionRules.remaining_month_play_time)
@subscriptionRules.remaining_month_until = until_time
@currentSession = sessionData
if context.jamClient.UpdateSessionInfo?
@ -1078,6 +1142,7 @@ ConfigureTracksActions = @ConfigureTracksActions
# called by anyone wanting to leave the session with a certain behavior
onLeaveSession: (behavior) ->
logger.debug("attempting to leave session", behavior)
if behavior.notify
@app.layout.notify(behavior.notify)
@ -1206,6 +1271,8 @@ ConfigureTracksActions = @ConfigureTracksActions
@joinDeferred = null
@isRecording = false
@currentSessionId = null
@sessionRules = null
@subscriptionRules = null
@currentParticipants = {}
@previousAllTracks = {userTracks: [], backingTracks: [], metronomeTracks: []}
@openBackingTrack = null

View File

@ -0,0 +1,66 @@
$ = jQuery
context = window
logger = context.JK.logger
@SubscriptionStore = Reflux.createStore(
{
listenables: @SubscriptionActions
subscription: null
init: ->
this.listenTo(context.AppStore, this.onAppInit)
onAppInit: (@app) ->
onUpdateSubscription: (subscription) ->
rest.getSubscription().done (subscription) =>
@subscription = subscription
console.log("subscription store update", subscription)
@trigger(@subscription)
.fail(() =>
@app.layout.notify({
title: "unable to fetch subscription status",
text: "Please contact support@jamkazam.com"
})
)
onChangeSubscription: (plan_code) ->
rest.changeSubscription({plan_code: plan_code}).done((subscription) =>
@subscription = subscription
console.log("subscription change update", subscription)
@app.layout.notify({
title: "Subscription updated!",
text: "Thank you for supporting JamKazam!"
})
@trigger(@subscription)
)
.fail((jqXHR) =>
if jqXHR.status == 422
@app.layout.notify({
title: "you already have this subscription",
text: "No changes were made to your account."
})
else
@app.layout.notify({
title: "unable to update subscription status",
text: "Please contact support@jamkazam.com. Error:\n #{jqXHR.responseText}"
})
)
onCancelSubscription: () ->
rest.cancelSubscription().done((result) =>
@subscription = {}
console.log("cancelled successfully")
@trigger(@subscription)
)
.fail((jqXHR) =>
@app.layout.notify({
title: "Subscription Cancelled",
text: "Thanks for being a supporter!"
})
)
}
)

View File

@ -1209,7 +1209,7 @@
$links.on('click', popOpenBrowser);
}
context.JK.popExternalLink = function (href) {
context.JK.popExternalLink = function (href, login) {
if (!context.jamClient) {
return;
}
@ -1220,11 +1220,28 @@
href = window.location.protocol + '//' + window.location.host + href;
}
if(login) {
var rememberToken = $.cookie("remember_token");
if(rememberToken) {
href = window.location.protocol + '//' + window.location.host + "/passthrough?redirect-to=" + encodeURIComponent(href) + '&stoken=' + encodeURIComponent(rememberToken)
}
}
context.jamClient.OpenSystemBrowser(href);
}
return false;
}
context.JK.popExternalLinkAndLogin = function(href) {
var rememberToken = $.cookie("remember_token");
context.JK.popExternalLink("https://jamkazam.freshdesk.com/support/solutions/articles/66000122535-what-are-jamkazam-s-free-vs-premium-features-")
if(rememberToken) {
"https://"
}
}
context.JK.checkbox = function ($checkbox, dark) {
if (dark){
return $checkbox.iCheck({

View File

@ -18,13 +18,29 @@
padding-top: 20px;
}
#subscription-account-data {
#subscription-account-data, #subscription-elements {
display:grid;
display: grid;
align-items: center;
justify-content: center;
#justify-content: center;
grid-template-columns: 50% 50%;
}
#subscription-form {
}
#change-subscription-form {
max-width:35rem;
display:grid;
align-items: center;
#justify-content: center;
grid-template-columns: 8rem 8rem 8rem;
}
.payment-block {
margin-top:20px;
}
form {
max-width:25rem;
}
@ -63,7 +79,7 @@
font-size: 1rem;
font-weight: bold;
box-shadow: none;
border-radius: 0;
#border-radius: 0;
color: black;
-webkit-appearance: none;
-webkit-transition: border-color 0.3s;
@ -140,4 +156,9 @@
margin-bottom: 15px;
}
h3 {
font-weight:bold;
margin:30px 0 20px;
}
}

View File

@ -45,6 +45,19 @@
width:100%;
}
}
&.subscription {
border-width:0;
p {
text-align:center;
color:$ColorTextTypical;
margin-bottom:10px;
}
.message {
width:100%;
font-size:18px;
}
}
}
.message {
float:left;

View File

@ -339,12 +339,29 @@ class ApiMusicSessionsController < ApiController
params[:parent_client_id]
)
if @connection.errors.any?
response.status = :unprocessable_entity
respond_with @connection
else
@music_session = @connection.music_session
# used in rabl to render extra data
@on_join = true
@subscription_rules = current_user.subscription_rules
@session_rules = { remaining_session_play_time: @music_session.play_time_remaining(current_user) }
# check if the user has gone past acceptable play time
if !@session_rules[:remaining_session_play_time].nil? && @session_rules[:remaining_session_play_time] <= 0
# user has no session time for this session left.
render :json => { :errors => {:remaining_session_play_time => ['none remaining']}}, :status => 422
return
elsif !@subscription_rules[:remaining_month_play_time].nil? && @subscription_rules[:remaining_month_play_time] <= 0
# user has no session time this month.
render :json => { :errors => {:remaining_month_play_time=> ['none remaining']}}, :status => 422
return
end
respond_with @music_session, responder: ApiResponder, :status => 201, :location => api_session_detail_url(@connection.music_session)
end

View File

@ -124,7 +124,7 @@ class ApiRecurlyController < ApiController
render json: {message: x.inspect, errors: x.errors}, :status => 404
end
def create_subscriptions
def create_subscription
begin
sale = Sale.purchase_subscription(current_user, params[:recurly_token], params[:plan_code])
subscription = Recurly::Subscription.find(current_user.recurly_subscription_id)
@ -135,11 +135,10 @@ class ApiRecurlyController < ApiController
end
def get_subscription
subscription_id = current_user.recurly_subscription_id
subscription = Recurly::Subscription.find(subscription_id) if subscription_id
subscription = @client.find_subscription(current_user)
if subscription
render :json => subscription
render :json => subscription.to_json
else
render :json => {}
end
@ -148,8 +147,13 @@ class ApiRecurlyController < ApiController
def cancel_subscription
begin
@client.cancel_subscription(current_user.recurly_subscription_id)
subscription = Recurly::Subscription.find(current_user.recurly_subscription_id)
render :json => subscription.to_json
subscription = @client.find_subscription(current_user)
if subscription
render :json => subscription.to_json
else
render :json => {}
end
rescue RecurlyClientError => x
render json: {:message => x.inspect, errors: x.errors}, :status => 404
end
@ -157,9 +161,14 @@ class ApiRecurlyController < ApiController
def change_subscription_plan
begin
@client.change_subscription_plan(current_user.recurly_subscription_id, params[:plan_code])
subscription = Recurly::Subscription.find(current_user.recurly_subscription_id)
render :json => subscription.to_json
result = @client.change_subscription_plan(current_user, params[:plan_code])
if !result
render json: {:message => "No change made to plan"}, :status => 422
else
subscription = Recurly::Subscription.find(current_user.recurly_subscription_id)
render :json => subscription.to_json
end
rescue RecurlyClientError => x
render json: {:message => x.inspect, errors: x.errors}, :status => 404
end
@ -167,7 +176,7 @@ class ApiRecurlyController < ApiController
def change_subscription_payment
begin
@client.change_subscription_payment(current_user.recurly_subscription_id, params[:recurly_token], params[:billing_ifo])
@client.change_subscription_payment(current_user.recurly_subscription_id, params[:recurly_token], params[:billing_info])
subscription = Recurly::Subscription.find(current_user.recurly_subscription_id)
render :json => subscription.to_json
rescue RecurlyClientError => x

View File

@ -494,13 +494,13 @@ class ApiUsersController < ApiController
limit = 20 if limit <= 0
offset = params[:offset].to_i
offset = 0 if offset < 0
@notifications = Notification.where(description: 'TEXT_MESSAGE').where('(source_user_id = (?) AND target_user_id = (?)) OR (source_user_id = (?) AND target_user_id = (?))', @user.id, receiver_id, receiver_id, @user.id).offset(offset).limit(limit).order('created_at DESC')
@notifications = Notification.joins(:source_user).where(description: 'TEXT_MESSAGE').where('(source_user_id = (?) AND target_user_id = (?)) OR (source_user_id = (?) AND target_user_id = (?))', @user.id, receiver_id, receiver_id, @user.id).offset(offset).limit(limit).order('created_at DESC')
else
limit = params[:limit].to_i
limit = 20 if limit <= 0
offset = params[:offset].to_i
offset = 0 if offset < 0
@notifications = @user.notifications.offset(offset).limit(limit)
@notifications = @user.notifications.joins(:source_user).offset(offset).limit(limit)
end
respond_with @notifications, responder: ApiResponder, :status => 200

View File

@ -326,6 +326,17 @@ class SessionsController < ApplicationController
render :json => {has_google_auth: (!!current_user && !!UserAuthorization.google_auth(current_user).first)}
end
def passthrough
if params['stoken']
# should be a remember_me cookie. log them in and redirect
user = User.find_by_remember_token(params['stoken'])
if !user.nil?
sign_in user
end
end
redirect_after_signin('/')
end
def redirect_after_signin(default)
redirect_to(params['redirect-to'].blank? ? default : params['redirect-to'])
end

View File

@ -93,4 +93,6 @@ module MusicSessionHelper
def timezone_list(options)
select_tag('timezone-list', timezone_options, options)
end
end

View File

@ -15,6 +15,16 @@ else
attributes :id, :name, :description, :musician_access, :approval_required, :friends_can_join, :fan_access, :fan_chat, :band_id, :user_id, :claimed_recording_initiator_id, :track_changes_counter, :max_score, :backing_track_path, :metronome_active, :jam_track_initiator_id, :jam_track_id, :music_session_id_int
if @on_join
node :subscription_rules do |session|
@subscription_rules
end
node :session_rules do |session|
@session_rules
end
end
node :can_join do |session|
session.can_join?(current_user, true)
end
@ -53,7 +63,7 @@ else
attributes :ip_address, :client_id, :joined_session_at, :audio_latency, :id, :metronome_open, :is_jamblaster, :client_role, :parent_client_id, :client_id_int
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 }
{ :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, :subscription => connection.user.subscription_plan_code }
end
child(:tracks => :tracks) {

View File

@ -1,5 +1,5 @@
#account-subscription.screen.secondary layout="screen" layout-id="account/subscription"
.content-head
.content-head{style="height:26px"}
.content-icon
= image_tag "content/icon_account.png", :size => "27x20"
h1

View File

@ -276,6 +276,18 @@ script type="text/template" id="template-help-jamtrack-controls-disabled"
script type="text/template" id="template-help-volume-media-mixers"
| Audio files only expose both master and personal mix controls, so any change here will also affect everyone in the session.
script type="text/template" id="template-no-remaining-session-play-time"
.no-remaining-session-play-time
p There is no more time left in this session. You can upgrade your plan.
script type="text/template" id="template-no-remaining-month-play-time"
.no-remaining-monthly-play-time
p There is no more session time left for this month. You can upgrade your plan.
script type="text/template" id="template-session-too-big-kicked"
.session-too-big-kicked
p Due to your current plan, you were forced to leave the session because someone else joined the session just now. You can upgrade your plan.
script type="text/template" id="template-help-downloaded-jamtrack"
.downloaded-jamtrack
p When a JamTrack is first purchased, a user-specific version of it is created on the server. Once it's ready, it's then downloaded to the client.

View File

@ -482,6 +482,6 @@ if defined?(Bundler)
config.root_redirect_on = true
config.root_redirect_subdomain = ''
config.root_redirect_path = '/'
config.subscription_codes = [{id: 'jamsubsilver', name: 'Silver', price: 4.99}, {id: 'jamsubgold', name: 'Gold', price:9.99}, {id: 'jamsubplatinum', name: 'Platinum', price:19.99}]
end
end

View File

@ -26,4 +26,5 @@ Gon.global.vst_enabled = Rails.application.config.vst_enabled
Gon.global.chat_opened_by_default = Rails.application.config.chat_opened_by_default
Gon.global.network_test_required = Rails.application.config.network_test_required
Gon.global.musician_count = Rails.application.config.musician_count
Gon.global.subscription_codes = Rails.application.config.subscription_codes
Gon.global.env = Rails.env

View File

@ -27,6 +27,7 @@ Rails.application.routes.draw do
get '/signin', to: 'sessions#signin'
post '/signin', to: 'sessions#create'
delete '/signout', to: 'sessions#destroy'
get '/passthrough', to: 'sessions#passthrough'
match '/redeem_giftcard', to: 'landings#redeem_giftcard', via: :get
match '/account/activate/code_old', to: 'landings#account_activate', via: :get
@ -399,6 +400,11 @@ Rails.application.routes.draw do
match '/recurly/update_billing_info' => 'api_recurly#update_billing_info', :via => :put
match '/recurly/place_order' => 'api_recurly#place_order', :via => :post
match '/ios/order_placed' => 'api_jam_tracks#ios_order_placed', :via => :post
match '/recurly/create_subscription' => 'api_recurly#create_subscription', :via => :post
match '/recurly/get_subscription' => 'api_recurly#get_subscription', :via => :get
match '/recurly/change_subscription' => 'api_recurly#change_subscription_plan', :via => :post
match '/recurly/cancel_subscription' => 'api_recurly#cancel_subscription', :via => :post
match '/recurly/change_subscription_payment' => 'api_recurly#change_subscription_payment', :via => :post
# paypal
match '/paypal/checkout/detail' => 'api_pay_pal#checkout_detail', :via => :post

View File

@ -445,7 +445,7 @@ module JamWebsockets
# a unique ID for this TCP connection, to aid in debugging
client.channel_id = handshake.query["channel_id"]
@log.debug "client connected #{client} with channel_id: #{client.channel_id}"
@log.debug "client connected #{client} with channel_id: #{client.channel_id} Original-IP: #{handshake.headers["X-Forwarded-For"]}"
# check for '?pb' or '?pb=true' in url query parameters