first attempt at onboarder interface

This commit is contained in:
Seth Call 2018-01-22 21:50:45 -06:00
parent dacfc1abd3
commit 42205d4d87
29 changed files with 1360 additions and 122 deletions

View File

@ -8,117 +8,166 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do
config.sort_order = 'created_at DESC'
filter :email
filter :first_name
filter :last_name
filter :jamuser_full_name_or_email_cont, label: 'Name Or Email', as: :string
filter :created_at
filter :updated_at
form :partial => "form"
show do |user|
attributes_table do
row :id
row :email
row :admin
row :updated_at
row :created_at
row :musician
row :city
row :state
row :country
row :first_name
row :last_name
row :birth_date
row :gender
row :email_confirmed
row :remember_token
row "Session Ready" do |user|
div do
if user.ready_for_session_at
span do
'YES'
end
span do
br
end
span do
link_to("mark as not checked", mark_session_not_ready_admin_student_path(user.id), {confirm: "Mark as not ready for session?"})
end
else
span do
'NO'
end
span do
br
end
span do
link_to("mark as checked", mark_session_ready_admin_student_path(user.id), {confirm: "Mark as ready for session?"})
end
panel "Common" do
attributes_table do
row :id
row :email
row :admin
row :updated_at
row :created_at
row :musician
row :city
row :state
row :country
row :first_name
row :last_name
row :birth_date
row :gender
row :email_confirmed
row :remember_token
row "Session Ready" do |user|
div do
if user.ready_for_session_at
span do
'YES'
end
span do
br
end
span do
link_to("mark as not checked", mark_session_not_ready_admin_student_path(user.id), {confirm: "Mark as not ready for session?"})
end
else
span do
'NO'
end
span do
br
end
span do
link_to("mark as checked", mark_session_ready_admin_student_path(user.id), {confirm: "Mark as ready for session?"})
end
end
end
end
end
row :jamclass_credits
row :via_amazon
row "Web Profile" do
link_to "Link", "#{Rails.application.config.external_root_url}/client#/profile/#{user.id}"
end
row :image do user.photo_url ? image_tag(user.photo_url) : '' end
row "Taken Lessons" do
table_for user.taken_lessons.order('created_at desc') do
column "View" do |lesson_session| link_to("View", lesson_session.admin_url) end
column :created_at
column :status
column "Teacher" do |lesson_session|
teacher = lesson_session.teacher
span do
link_to teacher.admin_name, teacher.admin_url
end
end
column "Start Time" do |lesson_session|
span do
lesson_session.music_session.pretty_scheduled_start(true)
end
br
span do
lesson_session.music_session.scheduled_start
end
end
row :jamclass_credits
row :via_amazon
row "Web Profile" do
link_to "Link", "#{Rails.application.config.external_root_url}/client#/profile/#{user.id}"
end
end
row "Taught Lessons" do
table_for user.taught_lessons.order('created_at desc') do
column "View" do |lesson_session| link_to("View", lesson_session.admin_url) end
column :created_at
column :status
column "Student" do |lesson_session|
student = lesson_session.student
span do
link_to student.admin_name, student.admin_name
end
end
column "Start Time" do |lesson_session|
span do
lesson_session.music_session.pretty_scheduled_start(true)
end
br
span do
lesson_session.music_session.scheduled_start
end
end
row :image do
user.photo_url ? image_tag(user.photo_url) : ''
end
end
end
panel "Onboarding" do
attributes_table do
row :onboarding_status
row "Support Consultant" do |user|
if user.onboarder
link_to "#{user.onboarder.name} (#{user.onboarder.onboarding_users.count})", user.onboarder.admin_url
else
end
end
row "Signup" do user.created_at.to_date end
row "Assigned", :onboarder_assigned_at
row "Email 1", :onboarding_email_1_sent_at
row "Email 2", :onboarding_email_2_sent_at
row "Email 3", :onboarding_email_3_sent_at
row "Email 4", :onboarding_email_4_sent_at
row "Email 5", :onboarding_email_5_sent_at
row "Test Session Scheduled Time", :onboarding_test_session_at
row "When Test Session Was Requested", :onboarding_test_session_at
row "Test Session Outcome", :onboarding_test_session_outcome
row "Notes", :onboarding_onboarder_notes
row "Lost Reason", :onboarding_lost_reason
row "Lost At", :onboarding_lost_at
row "Escalated Reason", :onboarding_escalation_reason
row "Escalated At", :onboarding_escalated_at
end
end
panel "Lessons" do
attributes_table do
row "Taken Lessons" do
table_for user.taken_lessons.order('created_at desc') do
column "View" do |lesson_session|
link_to("View", lesson_session.admin_url)
end
column :created_at
column :status
column "Teacher" do |lesson_session|
teacher = lesson_session.teacher
span do
link_to teacher.admin_name, teacher.admin_url
end
end
column "Start Time" do |lesson_session|
span do
lesson_session.music_session.pretty_scheduled_start(true)
end
br
span do
lesson_session.music_session.scheduled_start
end
end
end
end
row "Taught Lessons" do
table_for user.taught_lessons.order('created_at desc') do
column "View" do |lesson_session|
link_to("View", lesson_session.admin_url)
end
column :created_at
column :status
column "Student" do |lesson_session|
student = lesson_session.student
span do
link_to student.admin_name, student.admin_name
end
end
column "Start Time" do |lesson_session|
span do
lesson_session.music_session.pretty_scheduled_start(true)
end
br
span do
lesson_session.music_session.scheduled_start
end
end
end
end
end
end
active_admin_comments
end
index do
# actions # use this for all view/edit/delete links
column "Actions" do |user|
links = ''.html_safe
links << link_to("View", resource_path(user), :class => "member_link view_link")
links << link_to("Edit", edit_resource_path(user), :class => "member_link edit_link")
links
end
column "ID" do |user|
link_to(truncate(user.id, {:length => 12}),
resource_path(user),
link_to(truncate(user.id, {:length => 12}),
resource_path(user),
{:title => user.id})
end
column "Email" do |user|
@ -127,7 +176,9 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do
column :admin
column :updated_at
column :created_at
column :musician do |user| user.musician? ? true : false end
column :musician do |user|
user.musician? ? true : false
end
column :city
column :state
column :country
@ -139,14 +190,6 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do
column :photo_url
column :session_settings
column :can_invite
# actions # use this for all view/edit/delete links
column "Actions" do |user|
links = ''.html_safe
links << link_to("View", resource_path(user), :class => "member_link view_link")
links << link_to("Edit", edit_resource_path(user), :class => "member_link edit_link")
links
end
end
controller do
@ -177,7 +220,8 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do
@user = resource
@user.email = params[:jam_ruby_user][:email]
@user.admin = params[:jam_ruby_user][:admin]
@user.musician = params[:jam_ruby_user][:musician]
@user.is_onboarder = params[:jam_ruby_user][:is_onboarder]
@user.musician = params[:jam_ruby_user][:musician]
@user.first_name = params[:jam_ruby_user][:first_name]
@user.last_name = params[:jam_ruby_user][:last_name]
@user.state = params[:jam_ruby_user][:state]
@ -199,7 +243,7 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do
if params[:jam_ruby_user][:configure_video_no_show].to_i == 1
@user.mod_merge({User::MOD_NO_SHOW => {User::CONFIGURE_VIDEO_NOSHOW=> true}})
@user.mod_merge({User::MOD_NO_SHOW => {User::CONFIGURE_VIDEO_NOSHOW => true}})
else
@user.delete_mod(User::MOD_NO_SHOW, User::CONFIGURE_VIDEO_NOSHOW)
end

View File

@ -0,0 +1,85 @@
ActiveAdmin.register JamRuby::User, :as => 'CurrentlyOnboarding' do
menu :label => 'Currently Onboarding', :parent => 'JamClass'
config.sort_order = 'created_at desc'
config.batch_actions = true
config.per_page = 100
config.paginate = true
config.filters = true
config.clear_action_items!
batch_action :destroy, false
batch_action :onboarder, form: {
support_consultant: (User.where(is_onboarder: true).includes(:onboarding_users).map {|user| ["#{user.name} (#{user.onboarding_users.length})", user.id]}).to_a.unshift(['Unassign', ''])
} do |ids, inputs|
onboarder = inputs[:support_consultant]
if onboarder.blank?
onboarder = nil
else
onboarder = User.find(onboarder)
end
ids.each do |id|
user = User.find(id)
user.onboarder = onboarder
user.onboarder_assigned_at = Date.today
user.save
end
if onboarder
msg = 'Assigned ' + User.find(onboarder).name + " to #{ids.length} users"
else
msg = "Unassigned any Support Consultant from #{ids.length} users"
end
redirect_to :back, notice: msg
end
filter :jamuser_full_name_or_email_cont, label: 'Name Or Email', as: :string
filter :onboarder, as: :select, :collection => User.where(is_onboarder: true), label: 'Support Consultant'
filter :onboarder_id_blank, :as => :boolean, label: 'Unassigned'
filter :onboarding_escalation_reason_present, :as => :boolean, label: 'Escalated'
scope("TestDrive/Amazon Users", default: true) { |scope| scope.joins(:posa_cards).where('posa_cards.lesson_package_type_id in (?)', LessonPackageType::AMAZON_PACKAGES + LessonPackageType::LESSON_PACKAGE_TYPES) }
controller do
active_admin_config.includes.push :onboarding_users
end
index do
selectable_column
column "Name" do |user|
link_to user.name, user.admin_url
end
column :email
column :onboarding_status
column "Lost Reason", :onboarding_lost_reason
column "Escalated Reason", :onboarding_escalation_reason
column "Support Consultant" do |user|
if user.onboarder
link_to "#{user.onboarder.name} (#{user.onboarder.onboarding_users.count})", user.onboarder.admin_url
else
end
end
column "Signup" do |user|
user.created_at.to_date
end
column "Assigned", :onboarder_assigned_at
column "Email 1", :onboarding_email_1_sent_at
column "Email 2", :onboarding_email_2_sent_at
column "Email 3", :onboarding_email_3_sent_at
column "Email 4", :onboarding_email_4_sent_at
column "Email 5", :onboarding_email_5_sent_at
column "Test Session", :onboarding_test_session_scheduled_at
end
member_action :update_onboarder, :method => :post do
resource.onboarder = params[:onboarder]
resource.save!
redirect_to :back
end
end

View File

@ -15,7 +15,8 @@ ActiveAdmin.register JamRuby::User, :as => 'Students' do
filter :jamuser_full_name_or_email_cont, label: 'Name Or Email', as: :string
filter :school, label: 'School'
scope("Default", default: true) { |scope| scope.where('is_a_student = true OR jamclass_credits > 0 OR ((select count(id) from lesson_bookings where lesson_bookings.user_id = users.id) > 0)').order('users.ready_for_session_at IS NULL DESC') }
scope("TestDrive/Amazon Users", default: true) {|scope| scope.joins(:posa_cards).where('posa_cards.lesson_package_type_id in (?)', LessonPackageType::AMAZON_PACKAGES + LessonPackageType::LESSON_PACKAGE_TYPES) }
scope("Student Or Has Credits Or Has Lesson") { |scope| scope.where('is_a_student = true OR jamclass_credits > 0 OR ((select count(id) from lesson_bookings where lesson_bookings.user_id = users.id) > 0)').order('users.ready_for_session_at IS NULL DESC') }
index do
column "Name" do |user|

View File

@ -1,6 +1,4 @@
//= require active_admin/base
//= require jquery3
//= require jquery_ujs
//= require activeadmin_addons/all
// //= require jquery-ui

View File

@ -1 +1,2 @@
#= require active_admin/base
#= require jquery3

View File

@ -10,5 +10,3 @@
// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
// GO AFTER THE REQUIRES BELOW.
//
//= require jquery
//= require jquery_ujs

View File

@ -2,6 +2,7 @@
= f.inputs "Details" do
= f.input :email, label: 'Email'
= f.input :admin
= f.input :is_onboarder, label: 'Is Support Consultant'
= f.input :first_name
= f.input :last_name
= f.input :city

View File

@ -380,4 +380,5 @@ amazon_v1.sql
sms_index_optimize.sql
amazon_signup.sql
age_out_sessions.sql
alter_crash_dumps.sql
alter_crash_dumps.sql
onboarding.sql

22
db/up/onboarding.sql Normal file
View File

@ -0,0 +1,22 @@
ALTER TABLE users ADD COLUMN is_onboarder BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE users ADD COLUMN onboarding_status VARCHAR DEFAULT 'Unassigned';
ALTER TABLE users ADD COLUMN onboarding_lost_reason VARCHAR;
ALTER TABLE users ADD COLUMN onboarding_lost_at DATE;
ALTER TABLE users ADD COLUMN onboarding_escalation_reason VARCHAR;
ALTER TABLE users ADD COLUMN onboarding_escalated_at date;
ALTER TABLE users ADD COLUMN onboarder_id VARCHAR(64) REFERENCES users ON DELETE SET NULL;
ALTER TABLE users ADD COLUMN onboarder_assigned_at DATE;
ALTER TABLE users ADD COLUMN onboarding_email_1_sent_at DATE;
ALTER TABLE users ADD COLUMN onboarding_email_2_sent_at DATE;
ALTER TABLE users ADD COLUMN onboarding_email_3_sent_at DATE;
ALTER TABLE users ADD COLUMN onboarding_email_4_sent_at DATE;
ALTER TABLE users ADD COLUMN onboarding_email_5_sent_at DATE;
ALTER TABLE users ADD COLUMN onboarding_test_session_scheduled_at DATE;
ALTER TABLE users ADD COLUMN onboarding_test_session_at timestamp without time zone;
ALTER TABLE users ADD COLUMN onboarding_test_session_outcome VARCHAR;
ALTER TABLE users ADD COLUMN onboarding_onboarded_at DATE;
ALTER TABLE users ADD COLUMN onboarding_onboarder_notes VARCHAR;
ALTER TABLE users ADD COLUMN first_onboarding_free_lesson_at timestamp without time zone;
ALTER TABLE users ADD COLUMN first_onboarding_paid_lesson_at timestamp without time zone;

View File

@ -116,6 +116,10 @@ module JamRuby
description(lesson_booking)
end
def is_free?
# if it's a 0 dollar test drive, or the 'single free' lesson, then it's atually free
(is_test_drive && price == 0.00) || is_single_free
end
def is_single_free?
id == SINGLE_FREE
end

View File

@ -173,7 +173,7 @@ module JamRuby
# give 2 days to auto-cancel; this lets people just jump in a session and mark it done
def self.auto_cancel
MusicSession.joins(lesson_session: :lesson_booking).where('lesson_sessions.status = ? OR lesson_bookings.status = ?', LessonSession::STATUS_REQUESTED, LessonBooking::STATUS_COUNTERED).where("? > scheduled_start + (INTERVAL '2 days' * (duration))", Time.now).each do |music_session|
MusicSession.joins(lesson_session: :lesson_booking).where('lesson_sessions.status = ? OR lesson_bookings.status = ?', LessonSession::STATUS_REQUESTED, LessonBooking::STATUS_COUNTERED).where("? > scheduled_start + (INTERVAL '2 days') + (INTERVAL '1 minutes' * (duration))", Time.now).each do |music_session|
lesson_session = music_session.lesson_session
lesson_session.autocancel
end
@ -218,6 +218,10 @@ module JamRuby
counterer_id.nil? || counterer_id == student_id
end
def lesson_is_free?
lesson_booking.booked_price == 0.00
end
def mark_lesson(success, administratively_marked = false)
if !self.success && (is_requested? || is_unconfirmed?)
# what's going on here is that we will just mark a session as a success if the the people got in in case of requested sessions
@ -291,6 +295,24 @@ module JamRuby
return
end
# record how it went
if analysis[:student_analysis] && analysis[:student_analysis][:total_time] > 0
if lesson_is_free?
if student.first_onboarding_free_lesson_at.nil?
student.first_onboarding_free_lesson_at = Time.now
student.save
end
else
if student.first_onboarding_paid_lesson_at.nil?
student.first_onboarding_paid_lesson_at = Time.now
student.save
end
end
end
mark_lesson(analysis[:bill])
end

View File

@ -32,6 +32,38 @@ module JamRuby
MINIMUM_AUDIO_LATENCY = 2
MAXIMUM_AUDIO_LATENCY = 10000
ONBOARDING_STATUS_UNASSIGNED = "Unassigned"
ONBOARDING_STATUS_ASSIGNED = "Assigned"
ONBOARDING_STATUS_EMAILED = "Emailed"
ONBOARDING_STATUS_ONBOARDED = "Onboarded"
ONBOARDING_STATUS_LOST = "Lost"
ONBOARDING_STATUS_ESCALATED = "Escalated"
ONBOARDING_STATUS_FREE_LESSON = "Free Lesson Taken"
ONBOARDING_STATUS_PAID_LESSON = "Paid Lesson Taken"
ONBOARDING_STATUES = [ONBOARDING_STATUS_UNASSIGNED, ONBOARDING_STATUS_ASSIGNED, ONBOARDING_STATUS_EMAILED, ONBOARDING_STATUS_ONBOARDED, ONBOARDING_STATUS_LOST, ONBOARDING_STATUS_ESCALATED, ONBOARDING_STATUS_FREE_LESSON, ONBOARDING_STATUS_PAID_LESSON ]
SESSION_OUTCOME_SUCCESSFUL = "Successful"
SESSION_OUTCOME_SETUP_WIZARD_FAILURE = "Setup Wizard Failure"
SESSION_OUTCOME_NO_AUDIO_STREAM = "No Audio Stream in Session"
SESSION_OUTCOME_NO_VIDEO_STREAM = "No Video Stream In Session"
SESSION_OUTCOME_OTHER = "Other"
SESSION_OUTCOMES = [SESSION_OUTCOME_SUCCESSFUL, SESSION_OUTCOME_SETUP_WIZARD_FAILURE, SESSION_OUTCOME_NO_AUDIO_STREAM, SESSION_OUTCOME_NO_VIDEO_STREAM, SESSION_OUTCOME_OTHER]
LOST_REASON_LOST_INTEREST = "Lost Interest"
LOST_REASON_NO_COMPUTER = "No Win/Mac Computer"
LOST_REASON_NO_BROADBAND = "No Broadband Internet"
LOST_REASON_NO_WEBCAM = "No Webcam"
LOST_REASON_BAD_INTERNET = "Bad Internet"
LOST_REASON_SETUP_WIZARD_FAILURE = "Setup Wizard Failure"
LOST_REASON_NO_AUDIO_STREAM = "No Audio Stream In Session"
LOST_REASON_NO_VIDEO_STREAM = "No Video Stream In Session"
LOST_REASON_OTHER = "Other"
LOST_REASONS = [LOST_REASON_LOST_INTEREST, LOST_REASON_NO_COMPUTER, LOST_REASON_NO_BROADBAND, LOST_REASON_NO_WEBCAM, LOST_REASON_BAD_INTERNET, LOST_REASON_SETUP_WIZARD_FAILURE, LOST_REASON_NO_AUDIO_STREAM, LOST_REASON_NO_VIDEO_STREAM, LOST_REASON_OTHER]
ESCALATION_REASON_NO_AUDIO_STREAM = "No Audio Stream In Session"
ESCALATION_REASON_NO_VIDEO_STREAM = "No Video Stream In Session"
ESCALATION_REASON_SETUP_WIZARD_FAILURE = "Setup Wizard Failure"
ESCALATION_REASON_OTHER = "Other"
ESCALATION_REASONS = [ESCALATION_REASON_NO_AUDIO_STREAM, ESCALATION_REASON_NO_VIDEO_STREAM, ESCALATION_REASON_SETUP_WIZARD_FAILURE, ESCALATION_REASON_OTHER]
devise :database_authenticatable, :recoverable, :rememberable
acts_as_mappable
@ -203,6 +235,8 @@ module JamRuby
has_many :taken_lessons, :class_name => "JamRuby::LessonSession", inverse_of: :user, foreign_key: :user_id
has_many :taught_lessons, :class_name => "JamRuby::LessonSession", inverse_of: :teacher, foreign_key: :teacher_id
belongs_to :school, :class_name => "JamRuby::School", inverse_of: :students
belongs_to :onboarder, :class_name => "JamRuby::User", inverse_of: :onboarding_users, foreign_key: :onboarder_id
has_many :onboarding_users, :class_name => "JamRuby::User", inverse_of: :onboarder, foreign_key: :onboarder_id
has_one :owned_school, :class_name => "JamRuby::School", inverse_of: :user
has_one :owned_retailer, :class_name => "JamRuby::Retailer", inverse_of: :user
has_many :test_drive_package_choices, :class_name =>"JamRuby::TestDrivePackageChoice"
@ -239,6 +273,7 @@ module JamRuby
validates :show_whats_next, :inclusion => {:in => [nil, true, false]}
validates :is_a_student, :inclusion => {:in => [true, false]}
validates :is_a_teacher, :inclusion => {:in => [true, false]}
validates :is_onboarder, :inclusion => {:in => [true, false, nil]}
#validates :mods, json: true
validates_numericality_of :last_jam_audio_latency, greater_than: MINIMUM_AUDIO_LATENCY, less_than: MAXIMUM_AUDIO_LATENCY, :allow_nil => true
validates :last_jam_updated_reason, :inclusion => {:in => [nil, JAM_REASON_REGISTRATION, JAM_REASON_NETWORK_TEST, JAM_REASON_FTUE, JAM_REASON_JOIN, JAM_REASON_IMPORT, JAM_REASON_LOGIN]}
@ -287,6 +322,16 @@ module JamRuby
retailer.save!
end
end
if onboarding_lost_reason_changed? ||
onboarding_escalation_reason_changed? ||
onboarder_id_changed? ||
onboarding_email_5_sent_at_changed? ||
first_onboarding_free_lesson_at_changed? ||
first_onboarding_paid_lesson_at_changed? ||
onboarding_onboarded_at
User.where(id: self.id).update_all(onboarding_status: self.computed_onboarding_status)
end
end
def update_teacher_pct
if teacher
@ -2006,6 +2051,10 @@ module JamRuby
APP_CONFIG.admin_root_url + "/admin/students" # should add id; not yet supported
end
def admin_onboarding_url
APP_CONFIG.admin_root_url + "/admin/currently_onboardings"
end
def jam_track_rights_admin_url
APP_CONFIG.admin_root_url + "/admin/jam_track_rights?q[user_id_equals]=#{id}&commit=Filter&order=created_at DESC"
end
@ -2443,6 +2492,26 @@ module JamRuby
student.school && self.teacher && self.teacher.school && student.school.id == self.teacher.school.id
end
def computed_onboarding_status
if first_onboarding_paid_lesson_at
ONBOARDING_STATUS_PAID_LESSON
elsif first_onboarding_free_lesson_at
ONBOARDING_STATUS_FREE_LESSON
elsif onboarding_onboarded_at
ONBOARDING_STATUS_ONBOARDED
elsif onboarding_lost_reason
ONBOARDING_STATUS_LOST
elsif onboarding_escalation_reason
ONBOARDING_STATUS_ESCALATED
elsif onboarding_email_5_sent_at
ONBOARDING_STATUS_EMAILED
elsif onboarder_id
ONBOARDING_STATUS_ASSIGNED
else
ONBOARDING_STATUS_UNASSIGNED
end
end
private
def create_remember_token
self.remember_token = SecureRandom.urlsafe_base64

View File

@ -82,6 +82,7 @@
affiliate_referral_count: userDetail.affiliate_referral_count,
owns_school: !!userDetail.owned_school_id,
owns_retailer: !!userDetail.owned_retailer_id,
is_onboarder: userDetail.is_onboarder,
webcamName: webcamName
} , { variable: 'data' }));
@ -148,6 +149,7 @@
$("#account-content-scroller").on('click', '#account-affiliate-partner-link', function(evt) {evt.stopPropagation(); navToAffiliates(); return false; } );
$("#account-content-scroller").on('click', '#account-school-link', function(evt) {evt.stopPropagation(); navToSchool(); return false; } );
$("#account-content-scroller").on('click', '#account-retailer-link', function(evt) {evt.stopPropagation(); navToRetailer(); return false; } );
$("#account-content-scroller").on('click', '#account-onboarder-link', function(evt) {evt.stopPropagation(); navToOnboarder(); return false; } );
}
function renderAccount() {
@ -215,6 +217,11 @@
window.location = '/client#/account/retailer'
}
function navToOnboarder() {
resetForm()
window.location = '/client#/account/onboarder'
}
// handle update avatar event
function updateAvatar(avatar_url) {
var photoUrl = context.JK.resolveAvatarUrl(avatar_url);

View File

@ -2812,6 +2812,38 @@
})
}
function listOnboardings(id) {
return $.ajax({
type: "GET",
url: '/api/users/' + id + '/onboardings',
dataType: "json",
contentType: 'application/json'
})
}
function getOnboarding(id) {
return $.ajax({
type: "GET",
url: '/api/users/' + id + '/onboardings',
dataType: "json",
contentType: 'application/json'
})
}
function updateOnboarding(options) {
options = options || {}
var id = options.id
delete options.id
return $.ajax({
type: 'POST',
url: '/api/users/' + id + '/onboardings',
dataType: 'json',
contentType: 'application/json',
data: JSON.stringify(options)
})
}
function initialize() {
return self;
@ -3062,6 +3094,9 @@
this.liveStreamTransition = liveStreamTransition;
this.getLiveStream = getLiveStream;
this.getBroadcast = getBroadcast;
this.listOnboardings = listOnboardings;
this.getOnboarding = getOnboarding;
this.updateOnboarding = updateOnboarding;
return this;
};
})(window, jQuery);

View File

@ -0,0 +1,424 @@
context = window
rest = context.JK.Rest()
logger = context.JK.logger
AppStore = context.AppStore
UserStore = context.UserStore
profileUtils = context.JK.ProfileUtils
@AccountOnboarderScreen = React.createClass({
mixins: [
Reflux.listenTo(AppStore, "onAppInit"),
Reflux.listenTo(UserStore, "onUserChanged")
]
shownOnce: false
screenVisible: false
updates: []
onAppInit: (@app) ->
@app.bindScreen('account/onboarder', {beforeShow: @beforeShow, afterShow: @afterShow, beforeHide: @beforeHide})
onRetailerChanged: (retailerState) ->
@setState(retailerState)
onUserChanged: (userState) ->
@getOnboardings(userState?.user)
@setState({user: userState?.user})
componentDidMount: () ->
@root = $(@getDOMNode())
componentDidUpdate: () ->
datePicked = @datePicked.bind(this)
@sessionDate = @root.find('.date-picker')
@sessionDate.each(() ->
$this = $(this)
id = $this.attr('data-id')
day = $this.attr('data-date')
console.log("id-day: #{id}-#{day}")
$this.datepicker({
dateFormat: "D M d yy",
onSelect: ((e, inst) ->
datePicked(id, 'onboarding_test_session_at')
)
})
# initialize day
if(day)
$this.datepicker("setDate", new Date(day))
)
toggleDate: (dateText, inst) ->
console.log("THIS", this)
console.log("IDID ID #{$(this).attr('data-id')}")
'onboarding_test_session_at'
beforeHide: (e) ->
@screenVisible = false
return true
beforeShow: (e) ->
LocationActions.load()
getOnboardings: (user) ->
if user?.id? && @screenVisible
@setState({fetchingOnboardings: true})
rest.listOnboardings(context.JK.currentUserId).done((onboardings) =>
@setState({onboardings: onboardings, fetchingOnboardings: false})
)
.fail(() =>
@setState({fetchingOnboardings: false})
@app.layout.notify({
title: "onboarding list failure",
text: "Unable to list out your onboarders. Please contact support@jamkazam.com"
})
)
afterShow: (e) ->
@screenVisible = true
logger.debug("AccountOnboardingScreen: afterShow")
logger.debug("after show", @state.user)
@getOnboardings(@state.user)
getInitialState: () ->
{
onboardings: [],
user: null,
updating: false
}
onCancel: (e) ->
e.preventDefault()
context.location.href = '/client#/account'
onUpdate: (e) ->
if e?
e.preventDefault()
if this.state.updating
return
if @updates.length == 0
return
update = @updates.pop()
@setState({updating: true})
rest.updateOnboarding(update).done((response) => @onUpdateDone(response)).fail((jqXHR) => @onUpdateFail(jqXHR))
updateRow: (onboarding) ->
for match in this.state.onboardings
if match.id == onboarding.id
$.extend(match, onboarding)
console.log("UPDATED ONBOARDING", onboarding)
break
this.setState({onboardings: this.state.onboardings})
onUpdateDone: (response) ->
this.setState({updating: false})
@updateRow(response)
if @updates.length > 0
@onUpdate()
onUpdateFail: (jqXHR) ->
this.setState({updating: false})
@app.layout.notify({
title: "update failure",
text: "Unable to update user. Please let us know which user and field you can not update."
})
if @updates.length > 0
@onUpdate()
updateField: (id, field, e) ->
if(e)
e.preventDefault()
console.log("Update Field Called for ", field)
$target = $(e.target)
console.log("TARGET", $target)
value = true
if $target.is('select')
value = $target.val()
options = {id: id}
options[field] = value
@queueUpdate(options)
queueUpdate: (update) ->
@updates.push(update)
@onUpdate()
createLinkField: (id, onboarding, field, display, timeLabel = null) ->
if onboarding[field]
email = `<span style={{whiteSpace:'no-wrap'}}>{timeLabel} {onboarding[field]}</span>`
else
email = `<a href='#' className="daystamper" onClick={this.updateField.bind(this, id, field)}>{display}</a>`
email
showLostBanner: (id, field) ->
lostHtml = 'Please choose a reason why the user was lost:<br/><br/>' +
'<select><option value=""></option><option value="Lost Interest">Lost Interest</option><option value="No Win/Mac Computer">No Win/Mac Computer</option><option value="No Broadband Internet">No Broadband Internet</option><option value="No Webcam">No Webcam</option><option value="Bad Internet">Bad Internet</option><option value="Setup Wizard Failure">Setup Wizard Failure</option><option value="No Audio Stream In Session">No Audio Stream In Session</option><option value="No Video Stream In Session">No Video Stream In Session</option><option value="Other">Other</option></select>'
$item = context.JK.Banner.showAlert(
{buttons: [{name: 'CANCEL', click: () -> console.log('cancel clicked')}], html: lostHtml});
$item.find('select').change((e)=> @updateField(id, field, e); context.JK.Banner.hide())
showEscalationBanner: (id, field) ->
lostHtml = 'Please choose a reason why the user needs escalating:<br/><br/>' +
'<select><option value=""></option><option value="No Audio Stream In Session">No Audio Stream In Session</option><option value="No Video Stream In Session">No Video Stream In Session</option><option value="Setup Wizard Failure">Setup Wizard Failure</option><option value="Other">Other</option></select>'
$item = context.JK.Banner.showAlert(
{buttons: [{name: 'CANCEL', click: () -> console.log('cancel clicked')}], html: lostHtml});
$item.find('select').change((e)=> @updateField(id, field, e); context.JK.Banner.hide())
createLostField: (id, onboarding) ->
field = 'onboarding_lost_reason'
if onboarding[field]
field = `<span>{
onboarding[field]
}</span>`
else
field = `<a href='#' className="daystamper" onClick={this.showLostBanner.bind(this, id, field)}>lost student</a>`
field
createEscalatedField: (id, onboarding) ->
field = 'onboarding_escalation_reason'
if onboarding[field]
field = `<span>{
onboarding[field]
}</span>`
else
field = `<a href='#' className="daystamper" onClick={this.showEscalationBanner.bind(this, id, field)}>escalate
student</a>`
field
createOnboardingField: (onboarding) ->
@createLinkField(onboarding.id, onboarding, 'onboarding_onboarded_at', 'onboarded successfully')
watchTextArea: (id, field, e) ->
$text = $(e.target)
if @textTimeout
clearTimeout(@textTimeout)
@textTimeout = null
@textTimeout = setTimeout(() =>
data = {id: id}
data[field] = $text.val()
@queueUpdate(data)
@textTimeout = null
, 3000)
datePicked: (id, field) ->
$tr = @root.find("tr[data-id='" + id + "']")
picker = $tr.find(".date-picker")
hour = $tr.find('.hour').val()
minute = $tr.find('.minute').val()
am_pm = $tr.find('.am_pm').val()
if hour? and hour != ''
hour = new Number(hour)
if am_pm == 'PM'
hour += 12
else
hour = null
if minute? and minute != ''
minute = new Number(minute)
else
minute = null
date = picker.datepicker("getDate")
if date?
date.setHours(hour)
date.setMinutes(minute)
data = {id: id}
data[field] = date.toString()
@queueUpdate(data)
mainContent: () ->
cancelClasses = {"button-orange": true, "update": true}
onboardings = []
if @state.fetchingOnboardings
onboardings.push(`<tr>
<td colSpan="6" style={{textAlign:'center'}}>FETCHING STUDENTS</td>
</tr>`)
else if @state.onboardings.length > 0
for onboarding in @state.onboardings
console.log("onboarding", onboarding)
hours = []
for hour in ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12']
if hour == '12'
key = '00'
else
key = hour
hours.push(`<option key={key} value={key}>{hour}</option>`)
minutes = []
for minute in ['00', '15', '30', '45']
minutes.push(`<option key={minute} value={minute}>{minute}</option>`)
am_pm = [`<option key="AM" value="AM">AM</option>`, `<option key="PM" value="PM">PM</option>`]
email1 = @createLinkField(onboarding.id, onboarding, 'onboarding_email_1_sent_at', 'sent email 1', 'email 1:')
email2 = @createLinkField(onboarding.id, onboarding, 'onboarding_email_2_sent_at', 'sent email 2', 'email 2:')
email3 = @createLinkField(onboarding.id, onboarding, 'onboarding_email_3_sent_at', 'sent email 3', 'email 3:')
email4 = @createLinkField(onboarding.id, onboarding, 'onboarding_email_4_sent_at', 'sent email 4', 'email 4:')
email5 = @createLinkField(onboarding.id, onboarding, 'onboarding_email_5_sent_at', 'sent email 5', 'email 5:')
scheduledSession = @createLinkField(onboarding.id, onboarding, 'onboarding_test_session_scheduled_at',
'scheduled session', 'scheduled session:')
sessionOutcomes = ['', "Successful", "Setup Wizard Failure", "No Audio Stream in Session",
"No Video Stream In Session", "Other"]
sessionOutcomesJSX = []
for sessionOutcome in sessionOutcomes
sessionOutcomesJSX.push(`<option key={sessionOutcome} value={sessionOutcome}>{sessionOutcome}</option>`)
selectSessionOutcome =
`<select value={onboarding.onboarding_test_session_outcome}
onChange={this.updateField.bind(this, onboarding.id, 'onboarding_test_session_outcome')}>{sessionOutcomesJSX}</select>`
session_at = onboarding.onboarding_test_session_at
amOrPm = 'AM'
currentHours = '01'
currentMinutes = '00'
if session_at
sessionTime = new Date(session_at)
session_at = sessionTime.toLocaleDateString()
currentHours = sessionTime.getHours()
if sessionTime.getHours() > 11
amOrPm = 'PM'
currentHours -= 12
currentMinutes = sessionTime.getMinutes()
active = onboarding.onboarding_status == 'Emailed' || onboarding.onboarding_status == 'Assigned'
if active
activeClass = 'active-row'
else
activeClass = 'inactive-row'
columns = []
if active
columns.push(`<td key="1">
<div className="info-unit">{email1}</div>
<div className="info-unit">{email2}</div>
<div className="info-unit">{email3}</div>
<div className="info-unit">{email4}</div>
<div className="info-unit">{email5}</div>
</td>`)
columns.push(`<td key="2">
<div className="info-unit" style={{textAlign:'center'}}>{scheduledSession}</div>
<div className="info-unit">
<label style={{marginBottom:'5px'}}>Test Session Date:</label> <input data-id={onboarding.id}
className="date-picker"
name="session-date" type="text"
data-date={session_at}></input>
<label style={{marginTop:'10px'}}>Test Session Time:</label>
<select value={currentHours}
onChange={this.datePicked.bind(this, onboarding.id, 'onboarding_test_session_at')}
className="hour">{hours}</select> : <select value={currentMinutes}
onChange={this.datePicked.bind(this, onboarding.id, 'onboarding_test_session_at')}
className="minute">{minutes}</select>
<select value={amOrPm} style={{marginLeft:'13px'}}
onChange={this.datePicked.bind(this, onboarding.id, 'onboarding_test_session_at')}
className="am_pm">{am_pm}</select>
</div>
<div className="info-unit"><label style={{marginTop:'10px'}}>Session Outcome:</label>
{selectSessionOutcome}</div>
</td>`)
columns.push(`<td key="3">
<div className="info-unit"
style={{marginBottom:'20px'}}>{this.createLostField(onboarding.id, onboarding)}</div>
<div className="info-unit">{this.createEscalatedField(onboarding.id, onboarding)}</div>
</td>`)
columns.push(`<td key="4">
<div className="info-unit">{this.createOnboardingField(onboarding)}</div>
</td>`)
else
columns.push(`<td colSpan="4" style={{textAlign:'center', fontSize:'18px'}}>
<span>{onboarding.onboarding_status}</span><br/><br/><span style={{fontSize:'12px', marginLeft:'10px'}}>This student will drop off your list now next time you visit this page.</span>
</td>`)
onboarding = `<tr className={activeClass} key={onboarding.id} data-id={onboarding.id}>
<td>
<div className="info-unit">Name: {onboarding.name}</div>
<div className="info-unit">Email: {onboarding.email}</div>
<div className="info-unit" style={{marginTop:'20px'}}>Assigned
on {onboarding.onboarder_assigned_at}</div>
</td>
{columns}
<td>
<div className="info-unit">
<textarea className="info-unit"
onChange={this.watchTextArea.bind(this, onboarding.id, 'onboarding_onboarder_notes')}>{onboarding.onboarding_onboarder_notes}</textarea>
</div>
</td>
</tr>`
onboardings.push(onboarding)
else
onboardings.push(`<tr>
<td colSpan="6" style={{textAlign:'center'}}>NO STUDENTS ASSIGNED</td>
</tr>`)
`<div className="account-block info-block">
<div>
<div className="instructions">
Manage the users here that you are responsible for onboarding.<br/><br/>
All links in the table below, when clicked, will immediately mark that item as done. For example, once
you send your first email to the user, click the <b>sent email 1</b> link.<br/><br/>
All other fields will automatically save as you use them, such as the <b>Session Outcome</b> dropdown,
and the <b>Notes</b> text box.
</div>
<table className="jamtable">
<thead>
<tr>
<th>User</th>
<th>Email</th>
<th>Test Session</th>
<th>Problems</th>
<th>Onboarded</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
{onboardings}
</tbody>
</table>
</div>
<div className="actions">
<a className={classNames(cancelClasses)} onClick={this.onCancel}>BACK</a>
</div>
</div>`
render: () ->
mainContent = @mainContent()
`<div className="content-body-scroller">
<div className="profile-header profile-head">
<div className="clearall"></div>
</div>
<div className="profile-body">
<div className="profile-wrapper">
<div className="main-content">
{mainContent}
<br />
</div>
</div>
</div>
</div>`
})

View File

@ -167,9 +167,10 @@ ProfileActions = @ProfileActions
window.location.href = '/client#/jamclass/test-drive-selection/' + user.id
else if response.remaining_test_drives > 0
if response.booked_with_teacher && !context.JK.currentUserAdmin
logger.debug("TeacherSearchScreen: teacher already test-drived")
context.JK.Banner.showAlert('TestDrive', "You have already taken a TestDrive lesson from this teacher. With TestDrive, you need to use your lessons on 4 different teachers to find one who is best for you. We're sorry, but you cannot take multiple TestDrive lessons from a single teacher.")
#logger.debug("TeacherSearchScreen: teacher already test-drived")
#context.JK.Banner.showAlert('TestDrive', "You have already taken a TestDrive lesson from this teacher. With TestDrive, you need to use your lessons on 4 different teachers to find one who is best for you. We're sorry, but you cannot take multiple TestDrive lessons from a single teacher.")
logger.debug("TeacherSearchScreen: user being sent to book a lesson")
window.location.href = '/client#/jamclass/book-lesson/test-drive_' + user.id
else
# send on to booking screen for this teacher
logger.debug("TeacherSearchScreen: user being sent to book a lesson")

View File

@ -0,0 +1,316 @@
@import "client/common";
#account-onboarder {
div[data-react-class="AccountOnboarderScreen"] {
height: 100%;
}
.daystamper {
white-space: nowrap;
color:#fc0;
}
.profile-header {
padding: 10px 30px !important;
}
.info-unit {
margin:5px 10px;
font-size:12px;
select {font-size:12px; margin-top:5px;}
textarea{font-size:12px;min-height:100px;min-width:200px;}
}
.instructions {
margin-bottom:20px;
}
td {
vertical-align:middle;
}
td > * {
vertical-align : middle;
}
label {
display: inline-block;
min-width: 200px;
}
input {
min-width:200px;
}
.hint {
margin-left: 200px;
font-size: 12px;
font-style: italic;
margin-top: 5px;
}
.iradio_minimal {
display: inline-block;
top: 4px;
margin-right: 5px;
}
.field {
margin-bottom: 30px;
&.stripe-connect {
margin-bottom: 10px;
label {
margin-bottom: 10px;
}
}
}
.usage-hint {
font-size:12px;
text-decoration:underline;
margin-left:10px;
}
.scooter {
margin-bottom:10px;
}
div.retailer-split {
margin-top:10px;
}
.store-header {
float: left;
padding-top: 10px;
font-size: 20px;
font-weight: bold;
}
.profile-nav a {
position: absolute;
text-align: center;
height: 100%;
width: 98%;
margin: 0 auto;
padding: 11px 0 0 0;
@include border-box_sizing;
}
.profile-tile {
width: 25%;
float: left;
@include border-box_sizing;
height: 40px;
position: relative;
}
.profile-body {
padding-top: 40px;
}
.profile-photo {
width: 16%;
@include border-box_sizing;
}
.profile-nav {
margin: 0;
width: 84%;
}
.profile-wrapper {
padding: 10px 20px
}
.main-content {
float: left;
@include border-box_sizing;
width: 84%;
}
.info-block {
min-height:400px;
h3 {
font-weight: normal;
font-size: 14px;
margin-bottom: 10px;
min-width: 200px;
display: inline-block;
}
h4 {
margin-bottom: 10px;
}
.section {
margin-bottom: 40px;
&.teachers {
clear: both;
}
}
table.jamtable {
font-size: 12px;
width: 100%;
}
table.jamtable tr.inactive-row {
background-color:gray;
}
}
.stripe-connect {
padding: 0;
border: 0;
background: transparent;
outline:transparent;
cursor:pointer;
}
.actions {
float: left;
margin-top: 30px;
margin-bottom: 10px;
}
a.cancel {
margin-left:3px;
}
.avatar-edit-link {
display:inline-block;
img {
max-width:200px;
}
}
.avatar-edit-link {
.hint {
margin-left:0;
}
}
.column {
width:50%;
@include border_box_sizing;
h3 {
float:left;
}
.invite-dialog {
float:right;
margin-right:2px;
}
&.column-left {
float:left;
padding-right:30px;
}
&.column-right {
float:right;
padding-left:30px;
}
.username {
max-width:40%;
font-size:16px;
color:white;
}
table {
width:100%;
}
td.description {
font-size:16px;
color: white;
vertical-align: top;
white-space: nowrap;
}
td.message {
color: $ColorTextTypical;
padding-left: 10px;
vertical-align: top;
text-align:right;
}
.detail-block {
display:inline-block;
font-size:12px;
}
.resend {
float:left;
}
.delete {
float:right;
}
.teacher-invites, .student-invites {
margin-bottom: 20px;
margin-top:40px;
font-size:12px;
min-height:40px;
p {
font-size:12px;
}
}
.teachers, .students {
margin-bottom:20px;
}
p {
font-size:12px;
margin-left:0;
}
.retailer-invitation {
margin-bottom:20px;
}
}
.retailer-user {
margin-bottom:20px;
.avatar {
position:absolute;
padding:1px;
width:32px;
height:32px;
background-color:#ed4818;
margin:0;
-webkit-border-radius:16px;
-moz-border-radius:16px;
border-radius:16px;
float:none;
}
.avatar img {
width: 32px;
height: 32px;
-webkit-border-radius:16px;
-moz-border-radius:16px;
border-radius:16px;
}
.usersname {
margin-left:56px;
line-height:32px;
vertical-align: middle;
display: inline-block;
}
select[name="regions"] {
margin-botom:30px;
}
select[name="cities"] {
margin-bottom: 30px;
}
.just-name {
display:block;
}
.just-email {
position: relative;
top: -14px;
font-size:12px;
}
.user-actions {
float: right;
line-height: 32px;
height: 32px;
vertical-align: middle;
font-size:12px;
}
}
p {
font-size:12px;
margin:0;
}
.split-input {
:after {
content: '%';
}
}
}

View File

@ -1,7 +1,7 @@
require 'sanitize'
class ApiUsersController < ApiController
before_filter :api_signed_in_user, :except => [:create, :calendar, :show, :signup_confirm, :auth_session_create, :complete, :finalize_update_email, :isp_scoring, :add_play, :crash_dump, :validate_data, :google_auth, :user_event]
before_filter :api_signed_in_user, :except => [:create, :calendar, :show, :signup_confirm, :auth_session_create, :complete, :finalize_update_email, :isp_scoring, :add_play, :crash_dump, :validate_data, :google_auth, :user_event, :onboardings, :update_onboarding, :show_onboarding]
before_filter :auth_user, :only => [:session_settings_show, :session_history_index, :session_user_history_index, :update, :delete, :authorizations, :test_drive_status,
:liking_create, :liking_destroy, # likes
:following_create, :following_show, :following_destroy, # followings
@ -1090,6 +1090,40 @@ class ApiUsersController < ApiController
@teacher = User.find(params[:teacher_id])
end
def onboardings
@onboardings = current_user.onboarding_users.where('onboarding_status in (?)', [User::ONBOARDING_STATUS_ASSIGNED, User::ONBOARDING_STATUS_EMAILED]).order(:onboarder_assigned_at)
end
def update_onboarding
user = User.find(params[:id])
if params[:onboarding_lost_reason]
user.onboarding_lost_reason = params[:onboarding_lost_reason]
user.onboarding_lost_at = Date.today
end
if params[:onboarding_escalation_reason]
user.onboarding_escalation_reason = params[:onboarding_escalation_reason]
user.onboarding_escalated_at = Date.today
end
user.onboarding_email_1_sent_at = Date.today if params[:onboarding_email_1_sent_at]
user.onboarding_email_2_sent_at = Date.today if params[:onboarding_email_2_sent_at]
user.onboarding_email_3_sent_at = Date.today if params[:onboarding_email_3_sent_at]
user.onboarding_email_4_sent_at = Date.today if params[:onboarding_email_4_sent_at]
user.onboarding_email_5_sent_at = Date.today if params[:onboarding_email_5_sent_at]
user.onboarding_test_session_scheduled_at = Date.today if params[:onboarding_test_session_scheduled_at]
user.onboarding_test_session_at = params[:onboarding_test_session_at] if params[:onboarding_test_session_at]
user.onboarding_test_session_outcome = params[:onboarding_test_session_outcome] if params[:onboarding_test_session_outcome]
user.onboarding_onboarded_at = Date.today if params[:onboarding_onboarded_at]
user.onboarding_onboarder_notes = params[:onboarding_onboarder_notes] if params[:onboarding_onboarder_notes]
user.save
user.reload
@onboarding = user
end
def show_onboarding
@onboarding = User.find(params[:id])
end
###################### RECORDINGS #######################
# def recording_index
# @recordings = User.recording_index(current_user, params[:id])

View File

@ -569,7 +569,7 @@ class LandingsController < ApplicationController
body = "Name: #{@current_user.name}\n"
body << "Email: #{@current_user.email}\n"
body << "Admin: #{@current_user.admin_student_url}\n"
body << "Admin: #{@current_user.admin_onboarding_url}\n"
body << "Package Details: \n"
body << " Package: #{card.lesson_package_type.id}\n"
body << " Credits: #{card.credits}\n"

View File

@ -0,0 +1,3 @@
object @onboardings
extends "api_users/show_onboarding"

View File

@ -1,7 +1,7 @@
object @user
attributes :id, :first_name, :last_name, :name, :city, :state, :country, :location, :online, :photo_url, :musician, :gender, :birth_date, :internet_service_provider, :friend_count, :liker_count, :like_count, :follower_count, :following_count,
:recording_count, :session_count, :biography, :favorite_count, :audio_latency, :upcoming_session_count, :age, :website, :skill_level, :reuse_card, :email_needs_verification, :is_a_teacher, :is_a_student
:recording_count, :session_count, :biography, :favorite_count, :audio_latency, :upcoming_session_count, :age, :website, :skill_level, :reuse_card, :email_needs_verification, :is_a_teacher, :is_a_student, :is_onboarder
node :location do |user|
if user.musician?

View File

@ -0,0 +1,3 @@
object @onboarding
attributes :id, :name, :email, :onboarding_status, :onboarder_assigned_at, :onboarding_email_1_sent_at, :onboarding_email_2_sent_at, :onboarding_email_3_sent_at, :onboarding_email_4_sent_at, :onboarding_email_5_sent_at, :onboarding_test_session_scheduled_at, :onboarding_test_session_outcome, :onboarding_test_session_at, :onboarding_onboarded_at, :onboarding_onboarder_notes, :onboarding_lost_reason, :onboarding_lost_at, :onboarding_escalation_reason, :onboarding_escalated_at

View File

@ -0,0 +1,3 @@
object @onboarding
extends "api_users/show_onboarding"

View File

@ -181,6 +181,22 @@
<br clear="all" />
{% } %}
{% if (data.is_onboarder) { %}
<hr />
<div class="account-left">
<h2>onboarding:</h2>
</div>
<div class="account-mid school">
<div class="whitespace">
<span class="school-info">Manage the JamKazam users that you are assigned to for onboarding.</span>
</div>
</div>
<div class="right">
<a id="account-onboarder-link" href="#" class="button-orange">UPDATE</a>
</div>
<br clear="all" />
{% } %}
{% if (data.owns_school) { %}
<hr />
<div class="account-left">

View File

@ -0,0 +1,9 @@
#account-onboarder.screen.secondary layout="screen" layout-id="account/onboarder"
.content-head
.content-icon
= image_tag "content/icon_account.png", :size => "27x20"
h1
| jamclass
= render "screen_navigation"
.content-body
= react_component 'AccountOnboarderScreen', {}

View File

@ -88,6 +88,7 @@
<%= render "account_payment_history" %>
<%= render "account_school" %>
<%= render "account_retailer" %>
<%= render "account_onboarder" %>
<%= render "inviteMusicians" %>
<%= render "hoverBand" %>
<%= render "hoverFan" %>

View File

@ -462,6 +462,11 @@ Rails.application.routes.draw do
match '/users/progression/social_promoted' => 'api_users#social_promoted', :via => :post
match '/users/progression/opened_jamtrack_web_player' => 'api_users#opened_jamtrack_web_player', :via => :post
# onboarding
match '/users/:id/onboardings' => 'api_users#onboardings', :via => :get
match '/users/:id/onboardings' => 'api_users#update_onboarding', :via => :post
match '/users/:id/onboardings' => 'api_users#show_onboarding', :via => :get
# events
match '/users/event/record' => 'api_users#user_event', :via => :post

View File

@ -240,5 +240,136 @@ describe "Test Drive", :js => true, :type => :feature, :capybara_feature => true
teacher_distribution.distributed.should be false
end
it "same teacher with posa card succeeds" do
PosaCard.activate(card_lessons, retailer)
card_lessons.reload
card_lessons.claim(user)
card_lessons.errors.any?.should be false
visit "/client#/teachers/search"
#Timecop.travel(Date.new(2016, 04, 01))
find('.teacher-search-result[data-teacher-id="' + teacher_user.id + '"] .try-test-drive').trigger(:click)
# no longer true
# TryTestDriveDialog shows
#find('.purchase-testdrive-now').trigger(:click)
# dismiss banner
fill_out_single_lesson(Time.now.to_date + 1, Time.now.to_date + 2)
find('#banner h1', text: 'Lesson Requested')
find('a.button-orange', text:'CLOSE').trigger(:click)
# we tell user they have test drive purchased, and take them to the teacher screen
#find('#banner h1', text: 'TestDrive Purchased')
#find('#banner .dialog-inner', text: "You have purchased #{4} TestDrive credits and have used 1 credit to request a JamClass with #{teacher_user.name}")
# dismiss banner
#find('a.button-orange', text:'CLOSE').trigger(:click)
# validate that we made a test drive purchase
lesson_package_purchase = LessonPackagePurchase.where(user_id: user.id).first
lesson_package_purchase.should_not be_nil
lesson_package_purchase.lesson_package_type.is_test_drive?.should be true
lesson_package_purchase.posa_card.should eql card_lessons
lesson_package_purchase.lesson_payment_charge.should be_nil
user.reload
user.remaining_test_drives.should eql 0
user.jamclass_credits.should eql 3
#lesson_package_purchase.amount_charged.should eql 49.99
user.sales.count.should eql 1
sale = user.sales.first
sale.order_total.should eql 49.99
sale.recurly_total_in_cents.should be_nil
user.reload
user.onboarding_status.should eql User::ONBOARDING_STATUS_UNASSIGNED
user.student_lesson_bookings.count.should eql 1
lesson_booking = user.student_lesson_bookings.first
lesson_booking.is_requested?.should be true
user.remaining_test_drives.should eql 0
lesson_booking.lesson_sessions.count.should eql 1
lesson_session1 = lesson_booking.lesson_sessions.first
# approve by teacher:
teacher_approve(lesson_session1)
successful_lesson(lesson_session1)
# then log back in as student
switch_user(user, "/client#/teachers/search")
user.most_recent_test_drive_purchase.should_not be_nil
# let's make sure we can ask for another test drive too!
teacher_user.teacher.ready_for_session_at = Time.now
teacher_user.teacher.save!
find('a.teacher-search-options').trigger(:click)
find('a.search-btn').trigger(:click)
find('.teacher-search-result[data-teacher-id="' + teacher_user.id + '"] .try-test-drive').trigger(:click)
find('h2', text: 'book testdrive lesson')
find('.booking-info', text: '3 TestDrive lesson credits')
fill_out_single_lesson(Time.now.to_date + 1, Time.now.to_date + 2)
# we tell user they have test drive purchased, and take them to the teacher screen
find('#banner h1', text: 'Lesson Requested')
# dismiss banner
find('a.button-orange', text:'CLOSE').trigger(:click)
user.student_lesson_bookings.count.should eql 2
lesson_booking2 = user.student_lesson_bookings.order(:created_at).last
lesson_booking2.teacher.should eql teacher_user
lesson_session2 = lesson_booking2.lesson_sessions[0]
lesson_session2.lesson_package_purchase.should_not be_nil
# approve by teacher:
teacher_approve(lesson_session2)
successful_lesson(lesson_session2)
LessonSession.hourly_check
lesson_session1.reload
lesson_session1.analysed.should be true
analysis = lesson_session1.analysis
analysis["reason"].should eql LessonSessionAnalyser::SUCCESS
lesson_session1.billing_attempts.should be_nil
lesson_session1.billed.should eql false
lesson_session1.success.should be true
LessonBooking.hourly_check
lesson_session1.reload
teacher_distribution = lesson_session1.teacher_distribution
teacher_distribution.amount_in_cents.should eql 1000
teacher_distribution.ready.should be true
teacher_distribution.distributed.should be false
lesson_session2.reload
teacher_distribution = lesson_session2.teacher_distribution
teacher_distribution.amount_in_cents.should eql 1000
teacher_distribution.ready.should be true
teacher_distribution.distributed.should be false
user.reload
user.first_onboarding_paid_lesson_at.should_not be_nil
user.onboarding_status.should eql User::ONBOARDING_STATUS_PAID_LESSON
end
end
end

View File

@ -22,7 +22,8 @@ def failed_lesson(lesson_session, advance_to_end = true)
end
def teacher_approve(lesson_session)
sign_out_poltergeist(validate: true)
#sign_out_poltergeist(validate: true)
sign_out
sign_in_poltergeist(lesson_session.teacher, password: 'foobar')
visit "/client#/jamclass/lesson-booking/" + lesson_session.id
find(".slot-decision-field[data-slot-id=\"#{lesson_session.lesson_booking.default_slot.id}\"] ins", visible: false).trigger(:click)
@ -35,21 +36,24 @@ end
def date_picker_format(date)
date.strftime('%a %b %d %Y')
end
def fill_out_single_lesson
def fill_out_single_lesson(first_date = Date.new(2016, 4, 17), second_date = Date.new(2016, 4, 18))
first = first_date.strftime('%a %d %b %Y')
second = second_date.strftime('%a %d %b %Y')
find('h2', text: 'book testdrive lesson')
find('.booking-info', text: 'If you need to cancel')
# book the lesson
fill_in "slot-1-date", with: "Sun Apr 17 2016"
fill_in "slot-1-date", with: first# "Sun Apr 17 2016"
#find('.slot.slot-1 input.hasDatepicker').trigger(:click)
# click 4-6
find('td a', text: '17').trigger(:click)
find('td a', text: first_date.day).trigger(:click)
#find('.slot.slot-2 input.hasDatepicker').trigger(:click)
# click 4-7
fill_in "slot-2-date", with: "Mon Apr 18 2016"
find('td a', text: '18').trigger(:click)
fill_in "slot-2-date", with: second #"Mon Apr 18 2016"
find('td a', text: second_date.day).trigger(:click)
fill_in 'user-description', with: 'abc def dog neck'