diff --git a/admin/Gemfile b/admin/Gemfile index 595d94904..be68523a5 100644 --- a/admin/Gemfile +++ b/admin/Gemfile @@ -83,6 +83,9 @@ gem 'sendgrid_toolkit', '>= 1.1.1' gem 'stripe' gem 'zip-codes' gem 'email_validator' +gem 'best_in_place' #, github: 'bernat/best_in_place' + + #group :libv8 do # gem 'libv8', "~> 4.5.95" diff --git a/admin/Gemfile.lock b/admin/Gemfile.lock index d029ef2ce..a946d9be3 100644 --- a/admin/Gemfile.lock +++ b/admin/Gemfile.lock @@ -95,6 +95,9 @@ GEM backports (3.11.1) bcrypt (3.1.11) bcrypt-ruby (3.0.1) + best_in_place (3.1.1) + actionpack (>= 3.2) + railties (>= 3.2) binding_of_caller (0.8.0) debug_inspector (>= 0.0.1) bootstrap-sass (2.0.4.0) @@ -628,6 +631,7 @@ DEPENDENCIES amqp (= 0.9.8) aws-sdk (~> 1) bcrypt-ruby (= 3.0.1) + best_in_place bootstrap-sass (= 2.0.4) bootstrap-will_paginate (= 0.0.6) bugsnag diff --git a/admin/README.md b/admin/README.md index 503c08107..badfd2200 100644 --- a/admin/README.md +++ b/admin/README.md @@ -11,8 +11,9 @@ Overtime we can add more administrative functions and views, but initially this Examples of: -Button on Show Page of Item: 'Send Client Update Notice' in jam_ruby_artifact_updates.rb +* Button on Show Page of Item: 'Send Client Update Notice' in jam_ruby_artifact_updates.rb +* Batch Updates in View page: onboarding.rb (CurrentlyOnboarding) Stuff that is probably breaky: -activeadmin_addons https://github.com/platanus/activeadmin_addons \ No newline at end of file +activeadmin_addons https://github.com/platanus/activeadmin_addons diff --git a/admin/app/admin/dashboard.rb b/admin/app/admin/dashboard.rb index 51903b216..e4c5ff6db 100644 --- a/admin/app/admin/dashboard.rb +++ b/admin/app/admin/dashboard.rb @@ -9,7 +9,8 @@ ActiveAdmin.register_page "Dashboard" do small ul do li link_to "Users", admin_users_path li link_to "Teachers", admin_teachers_path - li link_to "Onboarding Management", admin_currently_onboardings_path + li link_to "Onboarding Management", admin_onboarder_managements_path + li link_to "Onboarding Settings", admin_onboarders_path li link_to "Lesson Sessions", admin_lesson_sessions_path li link_to "Slow Lesson Responses", admin_slow_responses_path diff --git a/admin/app/admin/jam_ruby_users.rb b/admin/app/admin/jam_ruby_users.rb index cce181faf..60f1dda4f 100644 --- a/admin/app/admin/jam_ruby_users.rb +++ b/admin/app/admin/jam_ruby_users.rb @@ -16,6 +16,13 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do form :partial => "form" + member_action :delete_forever, :method => :get do + resource.permanently_delete + redirect_to :back, {notice: 'User email and login credentials have been permanently changed'} + end + + + show do |user| panel "Common" do attributes_table do @@ -60,6 +67,11 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do end end end + row "Delete Forever" do |user| + span do + link_to("delete forever", delete_forever_admin_user_path(user.id), :data => {:confirm => 'Are you sure?'}) + end + end row :jamclass_credits row :via_amazon @@ -243,11 +255,13 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do @user.email = params[:jam_ruby_user][:email] @user.admin = params[:jam_ruby_user][:admin] @user.is_onboarder = params[:jam_ruby_user][:is_onboarder] + @user.subscribe_email = params[:jam_ruby_user][:subscribe_email] @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] @user.city = params[:jam_ruby_user][:city] + @user.gifted_jamtracks = params[:jam_ruby_user][:gifted_jamtracks] if params[:jam_ruby_user][:show_frame_options].to_i == 1 diff --git a/admin/app/admin/onboarders.rb b/admin/app/admin/onboarders.rb new file mode 100644 index 000000000..bb84642bb --- /dev/null +++ b/admin/app/admin/onboarders.rb @@ -0,0 +1,91 @@ +ActiveAdmin.register JamRuby::User, :as => 'Onboarder' do + + menu :label => 'Onboarder Settings', :parent => 'JamClass' + + config.sort_order = 'created_at' + config.batch_actions = true + config.per_page = 100 + config.paginate = true + config.filters = false + config.clear_action_items! + batch_action :destroy, false + + scope("All Onboarders", default: true) { |scope| scope.where(is_onboarder: true).order(:created_at) } + + controller do + active_admin_config.includes.push :onboarding_users + + def update + resource.max_onboardings = params[:jam_ruby_user][:max_onboardings] + resource.save! + success.json {} + end + end + + + + index do + def last_week + @last_week_result ||= calculate_last_week + end + + def calculate_last_week + start_day = Date.today.beginning_of_week(:sunday).yesterday.beginning_of_week(:sunday) + end_day = start_day.end_of_week(:sunday) + result = [start_day, end_day] + result + end + + def this_week + @this_week_result ||= calculate_this_week + end + + def calculate_this_week + start_day = Date.today.beginning_of_week(:sunday) + end_day = start_day.end_of_week(:sunday) + result = [start_day, end_day] + result + end + + def week_display(week) + start_day = week[0] + end_day = week[1] + mmyy = start_day.strftime('%b') + "#{mmyy} #{start_day.day}-#{end_day.day}" + end + + def onboarding_select + array = [] + 100.times do |i| + array.push [i.to_s, i.to_s] + end + array + end + column "Name" do |user| + div do + div do + link_to user.name, user.admin_url + end + div do + user.email + end + end + end + column "Max Onboardings" do |user| + best_in_place user, :max_onboardings, as: :select, url: admin_onboarder_path(user),:collection => onboarding_select + end + column "Current Week #{week_display(this_week)}" do |user| + start_date, last_date = this_week + user.onboarding_users.where(onboarder_assigned_at: start_date..last_date).count + end + column "Previous Week #{week_display(last_week)}" do |user| + start_date, last_date = last_week + user.onboarding_users.where(onboarder_assigned_at: start_date..last_date).count + end + column "Current Student WIP" do |user| + user.onboarding_users.where('onboarding_status = ? OR onboarding_status = ?', User::ONBOARDING_STATUS_ASSIGNED, User::ONBOARDING_STATUS_EMAILED).count + end + end + + +end \ No newline at end of file diff --git a/admin/app/admin/onboarding.rb b/admin/app/admin/onboarding.rb index 0adaf3285..8e4358691 100644 --- a/admin/app/admin/onboarding.rb +++ b/admin/app/admin/onboarding.rb @@ -1,6 +1,6 @@ -ActiveAdmin.register JamRuby::User, :as => 'CurrentlyOnboarding' do +ActiveAdmin.register JamRuby::User, :as => 'OnboarderManagement' do - menu :label => 'Currently Onboarding', :parent => 'JamClass' + menu :label => 'Onboarder Management', :parent => 'JamClass' config.sort_order = 'created_at desc' config.batch_actions = true @@ -11,7 +11,7 @@ ActiveAdmin.register JamRuby::User, :as => 'CurrentlyOnboarding' do 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', '']) + support_consultant: (User.where(is_onboarder: true).includes(:onboarding_users).map {|user| ["#{user.name} (#{user.onboarding_users.where('onboarding_status = ? OR onboarding_status = ?', User::ONBOARDING_STATUS_ASSIGNED, User::ONBOARDING_STATUS_EMAILED).count})", user.id]}).to_a.unshift(['Unassign', '']) } } do |ids, inputs| onboarder = inputs[:support_consultant] if onboarder.blank? @@ -41,7 +41,11 @@ ActiveAdmin.register JamRuby::User, :as => 'CurrentlyOnboarding' do 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) } + scope("Unassigned", default: true) { |scope| scope.joins(:posa_cards).where(onboarding_status: User::ONBOARDING_STATUS_UNASSIGNED).where('posa_cards.lesson_package_type_id in (?)', LessonPackageType::AMAZON_PACKAGES + LessonPackageType::LESSON_PACKAGE_TYPES) } + scope("Escalated") { |scope| scope.joins(:posa_cards).where('onboarding_status = ?', User::ONBOARDING_STATUS_ESCALATED).where('posa_cards.lesson_package_type_id in (?)', LessonPackageType::AMAZON_PACKAGES + LessonPackageType::LESSON_PACKAGE_TYPES) } + scope("Needs Manual Email") { |scope| scope.joins(:posa_cards).where(onboarding_status: User::ONBOARDING_STATUS_ONBOARDED).where('(stuck_take_flesson = TRUE AND sent_admin_take_flesson_email_at is NULL) OR (stuck_take_2nd_flesson = TRUE AND sent_admin_take_2nd_flesson_email_at IS NULL) OR (stuck_take_plesson = TRUE AND sent_admin_take_plesson_email_at IS NULL)').where('posa_cards.lesson_package_type_id in (?)', LessonPackageType::AMAZON_PACKAGES + LessonPackageType::LESSON_PACKAGE_TYPES) } + scope("Assigned") { |scope| scope.joins(:posa_cards).where('onboarding_status = ? OR onboarding_status = ?', User::ONBOARDING_STATUS_ASSIGNED, User::ONBOARDING_STATUS_EMAILED).where('posa_cards.lesson_package_type_id in (?)', LessonPackageType::AMAZON_PACKAGES + LessonPackageType::LESSON_PACKAGE_TYPES) } + scope("All TestDrive/Amazon Users") { |scope| scope.joins(:posa_cards).where('posa_cards.lesson_package_type_id in (?)', LessonPackageType::AMAZON_PACKAGES + LessonPackageType::LESSON_PACKAGE_TYPES) } controller do @@ -59,10 +63,23 @@ ActiveAdmin.register JamRuby::User, :as => 'CurrentlyOnboarding' do 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 + link_to "#{user.onboarder.name} (#{user.onboarder.onboarding_users.where('onboarding_status = ? OR onboarding_status = ?', User::ONBOARDING_STATUS_ASSIGNED, User::ONBOARDING_STATUS_EMAILED).count})", user.onboarder.admin_url else end end + column "Manual Email Needed" do |user| + + div do + if user.stuck_take_plesson + link_to("sent take paid lesson email", mark_sent_paid_lesson_admin_onboarder_management_path(user.id), {'data-confirm': "You sent an email manually to the user to remind them to take a paid lesson?"}) + elsif user.stuck_take_2nd_flesson + link_to("sent take 2nd lesson email", mark_sent_2nd_free_lesson_admin_onboarder_management_path(user.id), {'data-confirm': "You sent an email manually to the user to remind them to take 2nd free lesson?"}) + elsif user.stuck_take_flesson + link_to("sent take 1st lesson email", mark_sent_1st_free_lesson_admin_onboarder_management_path(user.id), {'data-confirm': "You sent an email manually to the user to remind them to take a 1st free lesson?"}) + else + end + end + end column "Signup" do |user| user.created_at.to_date @@ -74,6 +91,17 @@ ActiveAdmin.register JamRuby::User, :as => 'CurrentlyOnboarding' do column "Email 4", :onboarding_email_4_sent_at column "Email 5", :onboarding_email_5_sent_at column "Test Session", :onboarding_test_session_scheduled_at + column "Admin Actions" do |user| + div do + if user.is_waiting_onboarding || user.is_onboarding + link_to("mark onboarded", mark_onboarded_admin_onboarder_management_path(user.id), { 'data-confirm': "Mark onboarded?"}) + end + if user.is_waiting_onboarding || user.is_onboarding + link_to("mark lost", mark_lost_admin_onboarder_management_path(user.id), { 'data-confirm': "Mark lost (reason = Other)?"}) + end + end + end + column "Onboarder Notes", :onboarding_onboarder_notes end member_action :update_onboarder, :method => :post do @@ -82,4 +110,29 @@ ActiveAdmin.register JamRuby::User, :as => 'CurrentlyOnboarding' do redirect_to :back end + member_action :mark_sent_paid_lesson, :method => :get do + resource.mark_sent_paid_lesson + redirect_to :back + end + + member_action :mark_sent_2nd_free_lesson, :method => :get do + resource.mark_sent_2nd_free_lesson + redirect_to :back + end + + member_action :mark_sent_1st_free_lesson, :method => :get do + resource.mark_sent_1st_free_lesson + redirect_to :back + end + + member_action :mark_onboarded, :method => :get do + resource.mark_onboarded + redirect_to :back + end + + member_action :mark_lost, :method => :get do + resource.mark_lost + redirect_to :back + end + end \ No newline at end of file diff --git a/admin/app/assets/javascripts/active_admin.js b/admin/app/assets/javascripts/active_admin.js index 84a187b5a..df9db3400 100644 --- a/admin/app/assets/javascripts/active_admin.js +++ b/admin/app/assets/javascripts/active_admin.js @@ -12,3 +12,8 @@ // //= require autocomplete-rails //= require base //= require_tree . + + +$(document).ready(function() { + jQuery(".best_in_place").best_in_place() +}) diff --git a/admin/app/assets/javascripts/active_admin.js.coffee b/admin/app/assets/javascripts/active_admin.js.coffee index e211bdfe7..7d9d7acbd 100644 --- a/admin/app/assets/javascripts/active_admin.js.coffee +++ b/admin/app/assets/javascripts/active_admin.js.coffee @@ -1,2 +1,10 @@ #= require active_admin/base #= require jquery3 +#= require best_in_place +#= require jquery.purr +#= require best_in_place.purr + +$(document).ready -> + # IS NOT HAPPENING: USE ACTIVE_ADMIN.JS + console.log("DAT COFFEE INIT") + jQuery(".best_in_place").best_in_place() \ No newline at end of file diff --git a/admin/app/helpers/application_helper.rb b/admin/app/helpers/application_helper.rb index 80731359d..6e9385e59 100644 --- a/admin/app/helpers/application_helper.rb +++ b/admin/app/helpers/application_helper.rb @@ -1,5 +1,4 @@ module ApplicationHelper - end diff --git a/admin/app/helpers/jam_sessions_helper.rb b/admin/app/helpers/jam_sessions_helper.rb index bf244b019..9ef3f0789 100644 --- a/admin/app/helpers/jam_sessions_helper.rb +++ b/admin/app/helpers/jam_sessions_helper.rb @@ -1,2 +1,3 @@ module JamSessionsHelper + end \ No newline at end of file diff --git a/admin/app/views/admin/users/_form.html.slim b/admin/app/views/admin/users/_form.html.slim index 2e8d483b9..df29c3115 100644 --- a/admin/app/views/admin/users/_form.html.slim +++ b/admin/app/views/admin/users/_form.html.slim @@ -3,6 +3,8 @@ = f.input :email, label: 'Email' = f.input :admin = f.input :is_onboarder, label: 'Is Support Consultant' + = f.input :subscribe_email, label: 'Subscribed to Emails?' + = f.input :gifted_jamtracks, label: 'JamTrack Credits' = f.input :first_name = f.input :last_name = f.input :city diff --git a/db/manifest b/db/manifest index 19a9059d2..b8a025a75 100755 --- a/db/manifest +++ b/db/manifest @@ -383,4 +383,8 @@ age_out_sessions.sql alter_crash_dumps.sql onboarding.sql better_lesson_notices.sql -teacher_search_control.sql \ No newline at end of file +teacher_search_control.sql +user_timezone.sql +onboarder_limit.sql +onboarding_emails.sql +limit_counter_reminders.sql \ No newline at end of file diff --git a/db/up/limit_counter_reminders.sql b/db/up/limit_counter_reminders.sql new file mode 100644 index 000000000..08d57e0a8 --- /dev/null +++ b/db/up/limit_counter_reminders.sql @@ -0,0 +1 @@ +ALTER TABLE lesson_sessions ADD COLUMN counter_reminders INTEGER NOT NULL DEFAULT 0; \ No newline at end of file diff --git a/db/up/onboarder_limit.sql b/db/up/onboarder_limit.sql new file mode 100644 index 000000000..2a2e35ba9 --- /dev/null +++ b/db/up/onboarder_limit.sql @@ -0,0 +1 @@ +ALTER TABLE users ADD COLUMN max_onboardings INTEGER NOT NULL DEFAULT 0; diff --git a/db/up/onboarding_emails.sql b/db/up/onboarding_emails.sql new file mode 100644 index 000000000..4e4d3113e --- /dev/null +++ b/db/up/onboarding_emails.sql @@ -0,0 +1,28 @@ +ALTER TABLE users ADD COLUMN sent_take_flesson_email_at TIMESTAMP WITHOUT TIME ZONE; +ALTER TABLE users ADD COLUMN sent_take_flesson_email_times INTEGER NOT NULL DEFAULT 0; +ALTER TABLE users ADD COLUMN sent_take_2nd_flesson_email_at TIMESTAMP WITHOUT TIME ZONE; +ALTER TABLE users ADD COLUMN sent_take_2nd_flesson_email_times INTEGER NOT NULL DEFAULT 0; +ALTER TABLE users ADD COLUMN sent_take_plesson_email_at TIMESTAMP WITHOUT TIME ZONE; +ALTER TABLE users ADD COLUMN sent_take_plesson_email_times INTEGER NOT NULL DEFAULT 0; +ALTER TABLE users ADD COLUMN second_onboarding_free_lesson_at timestamp without time zone; +ALTER TABLE users ADD COLUMN sent_admin_take_flesson_email_at TIMESTAMP WITHOUT TIME ZONE; +ALTER TABLE users ADD COLUMN sent_admin_take_2nd_flesson_email_at TIMESTAMP WITHOUT TIME ZONE; +ALTER TABLE users ADD COLUMN sent_admin_take_plesson_email_at TIMESTAMP WITHOUT TIME ZONE; +ALTER TABLE users ADD COLUMN stuck_take_flesson BOOLEAN NOT NULL DEFAULT FALSE; +ALTER TABLE users ADD COLUMN stuck_take_2nd_flesson BOOLEAN NOT NULL DEFAULT FALSE; +ALTER TABLE users ADD COLUMN stuck_take_plesson BOOLEAN NOT NULL DEFAULT FALSE; +ALTER TABLE teachers ADD COLUMN random_order INTEGER NOT NULL DEFAULT 0; +ALTER TABLE users ADD COLUMN send_onboarding_survey BOOLEAN NOT NULL DEFAULT FALSE; +ALTER TABLE users ADD COLUMN sent_onboarding_survey_at TIMESTAMP WITHOUT TIME ZONE; +CREATE INDEX index_first_onboarding_paid_lesson_at ON users USING btree(first_onboarding_paid_lesson_at); +CREATE INDEX index_onboarding_onboarded_at ON users USING btree(onboarding_onboarded_at); +CREATE INDEX index_onboarding_status ON users USING btree(onboarding_status); +CREATE INDEX index_stuck_take_plesson ON users USING btree(stuck_take_plesson); +CREATE INDEX index_stuck_take_2nd_flesson ON users USING btree(stuck_take_2nd_flesson); +CREATE INDEX index_stuck_take_flesson ON users USING btree(stuck_take_flesson); +CREATE INDEX index_sent_admin_take_flesson_email_at ON users USING btree(sent_admin_take_flesson_email_at); +CREATE INDEX index_sent_admin_take_2nd_flesson_email_at ON users USING btree(sent_admin_take_2nd_flesson_email_at); +CREATE INDEX index_sent_admin_take_plesson_email_at ON users USING btree(sent_admin_take_plesson_email_at); +CREATE INDEX index_posa_cards_lesson_package_type_id ON posa_cards USING btree(lesson_package_type_id); + +UPDATE teachers set random_order = sub.row_number * random() * 1000 from (select id, row_number() over () from teachers) as sub ; \ No newline at end of file diff --git a/db/up/user_timezone.sql b/db/up/user_timezone.sql new file mode 100644 index 000000000..7f0e0da1d --- /dev/null +++ b/db/up/user_timezone.sql @@ -0,0 +1,2 @@ +ALTER TABLE users ADD COLUMN timezone VARCHAR; +ALTER TABLE users ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE; \ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/mailers/admin_mailer.rb b/ruby/lib/jam_ruby/app/mailers/admin_mailer.rb index 79f5c2c0f..4d6a50682 100644 --- a/ruby/lib/jam_ruby/app/mailers/admin_mailer.rb +++ b/ruby/lib/jam_ruby/app/mailers/admin_mailer.rb @@ -52,6 +52,15 @@ module JamRuby subject: options[:subject]) end + def ugly(options) + mail(to: options[:to], + cc: options[:cc], + from: APP_CONFIG.email_generic_from, + body: options[:body], + content_type: "text/plain", + subject: options[:subject]) + end + def recurly_alerts(user, options) body = options[:body] diff --git a/ruby/lib/jam_ruby/app/mailers/user_mailer.rb b/ruby/lib/jam_ruby/app/mailers/user_mailer.rb index 296f75580..9cece2313 100644 --- a/ruby/lib/jam_ruby/app/mailers/user_mailer.rb +++ b/ruby/lib/jam_ruby/app/mailers/user_mailer.rb @@ -49,6 +49,92 @@ module JamRuby end end + def take_paid_lesson(user) + @user = user + @lesson = user.taken_lessons.order(:created_at).last + @teacher = @lesson.teacher + + if user.sent_take_plesson_email_times == 0 + @subject = "Book your next lesson with your instructor now!" + elsif user.sent_take_plesson_email_times == 1 + @subject = "Book your next lesson to continue your musical journey" + else + @subject = "The best way to improve on your instrument is with a great instructor - book today!" + end + sendgrid_category "promo_lesson_reminder" + sendgrid_unique_args :type => "promo_lesson_reminder" + + sendgrid_recipients([user.email]) + sendgrid_substitute('@USERID', [user.id]) + + mail(:to => user.email, :subject => @subject) do |format| + format.text + format.html + end + end + + def take_second_free_lesson(user) + @user = user + @lesson = user.taken_lessons.order(:created_at).last + @teacher = @lesson.teacher + + if user.sent_take_2nd_flesson_email_times == 0 + @subject = "Book your second free lesson now!" + elsif user.sent_take_2nd_flesson_email_times == 1 + @subject = "Book your second free lesson to continue your musical journey" + else + @subject = "Last reminder to book your next free lesson" + end + + sendgrid_category "promo_lesson_reminder" + sendgrid_unique_args :type => "promo_lesson_reminder" + + sendgrid_recipients([user.email]) + sendgrid_substitute('@USERID', [user.id]) + + mail(:to => user.email, :subject => @subject) do |format| + format.text + format.html + end + end + + def take_first_free_lesson(user) + @user = user + if user.sent_take_flesson_email_times == 0 + @subject = "Book your first free lesson now!" + elsif user.sent_take_flesson_email_times == 1 + @subject = "Book your first free lesson to start your musical journey" + else + @subject = "Last reminder to book your free lessons - $60 value!" + end + + sendgrid_category "promo_lesson_reminder" + sendgrid_unique_args :type => "promo_lesson_reminder" + + sendgrid_recipients([user.email]) + sendgrid_substitute('@USERID', [user.id]) + + mail(:to => user.email, :subject => @subject) do |format| + format.text + format.html + end + end + + def onboarding_survey(user) + @user = user + @subject = "1-minute JamKazam survey - please help us give good support!" + sendgrid_category "onboarding_survey" + sendgrid_unique_args :type => "onboarding_survey" + + sendgrid_recipients([user.email]) + sendgrid_substitute('@USERID', [user.id]) + + mail(:to => user.email, :subject => @subject) do |format| + format.text + format.html + end + end + def student_education_welcome_message(user) @user = user @subject = "Welcome to JamKazam and JamClass online lessons!" diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/onboarding_survey.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/onboarding_survey.html.erb new file mode 100644 index 000000000..f552b2d7b --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/onboarding_survey.html.erb @@ -0,0 +1,12 @@ + +<% if !@user.anonymous? %> +

Hi <%= @user.first_name %>, +

+<% end %> + +

+ Please click this link to take a literally 1-minute survey on your onboarding experience: https://www.surveymonkey.com/r/93JC2KJ. This will help us deliver better support to other students like you. Thank you! +

+ +

Best Regards,
+ Team JamKazam

\ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/onboarding_survey.text.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/onboarding_survey.text.erb new file mode 100644 index 000000000..91abd4b12 --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/onboarding_survey.text.erb @@ -0,0 +1,9 @@ +<% if !@user.anonymous? %> +Hi <%= @user.first_name %>, +<% end %> + +Please click this link to take a literally 1-minute survey on your onboarding experience: https://www.surveymonkey.com/r/93JC2KJ. This will help us deliver better support to other students like you. Thank you! + + +Best Regards, +Team JamKazam diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/take_first_free_lesson.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/take_first_free_lesson.html.erb new file mode 100644 index 000000000..b3055afbf --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/take_first_free_lesson.html.erb @@ -0,0 +1,45 @@ +<% provide(:title, @subject) %> + + +<% if !@user.anonymous? %> +

Hi <%= @user.first_name %>, +

+<% end %> + +<% if @user.sent_take_flesson_email_times == 0 %> + +

We hope you had a great support experience with the JamKazam consultant who prepared you to get into your first free online lesson. Life gets busy. So before you forget about it, please find an instructor who looks great for you, and book your first free lesson now! +

+ +

+ Here is an article that explains how to search for your ideal teacher: http://bit.ly/2kBPXBz. And here is an article that explains how to book your first lesson: http://bit.ly/2k3qNMT. +

+

+ If you have any trouble, please email us at support@jamkazam.com. Or you can call us at 877-376-8742. +

+ + <% elsif @user.sent_take_flesson_email_times == 1 %> + +

We noticed you haven't booked your first free lesson yet. Don't forget to take advantage of this amazing limited promotion. Your two free lessons are valued at approximately $60! Book your first lesson today.

+

+ Here is an article that explains how to search for your ideal teacher: http://bit.ly/2kBPXBz. And here is an article that explains how to book your first lesson: http://bit.ly/2k3qNMT. +

+

+ If you have any trouble, please email us at support@jamkazam.com. Or you can call us at 877-376-8742. +

+ +<% else %> + + +

It looks like you're busy, but we wanted to remind you once more to book your first free lesson before you forget and let this terrific promotion get away. Start your musical journey with a world-class instructor who can help you achieve your goals. There is no better way to learn and master your new instrument!

+

+ Here is an article that explains how to search for your ideal teacher: http://bit.ly/2kBPXBz. And here is an article that explains how to book your first lesson: http://bit.ly/2k3qNMT. +

+

+ If you have any trouble, please email us at support@jamkazam.com. Or you can call us at 877-376-8742. +

+ + <% end %> + +

Best Regards,
+ Team JamKazam

\ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/take_first_free_lesson.text.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/take_first_free_lesson.text.erb new file mode 100644 index 000000000..869994f72 --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/take_first_free_lesson.text.erb @@ -0,0 +1,26 @@ +<% if !@user.anonymous? %> +Hi <%= @user.first_name %>, +<% end %> + +<% if @user.sent_take_flesson_email_times == 0 %> +We hope you had a great support experience with the JamKazam consultant who prepared you to get into your first free online lesson. Life gets busy. So before you forget about it, please find an instructor who looks great for you, and book your first free lesson now! + +Here is an article that explains how to search for your ideal teacher: http://bit.ly/2kBPXBz. And here is an article that explains how to book your first lesson:http://bit.ly/2k3qNMT. + +If you have any trouble, please email us at support@jamkazam.com. Or you can call us at 877-376-8742. +<% elsif @user.sent_take_flesson_email_times == 1 %> +We noticed you haven't booked your first free lesson yet. Don't forget to take advantage of this amazing limited promotion. Your two free lessons are valued at approximately $60! Book your first lesson today. + +Here is an article that explains how to search for your ideal teacher: http://bit.ly/2kBPXBz. And here is an article that explains how to book your first lesson:http://bit.ly/2k3qNMT. + +If you have any trouble, please email us at support@jamkazam.com. Or you can call us at 877-376-8742. +<% else %> +It looks like you're busy, but we wanted to remind you once more to book your first free lesson before you forget and let this terrific promotion get away. Start your musical journey with a world-class instructor who can help you achieve your goals. There is no better way to learn and master your new instrument! + +Here is an article that explains how to search for your ideal teacher: http://bit.ly/2kBPXBz. And here is an article that explains how to book your first lesson:http://bit.ly/2k3qNMT. + +If you have any trouble, please email us at support@jamkazam.com. Or you can call us at 877-376-8742. +<% end %> + +Best Regards, +Team JamKazam \ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/take_paid_lesson.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/take_paid_lesson.html.erb new file mode 100644 index 000000000..0979d40fc --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/take_paid_lesson.html.erb @@ -0,0 +1,45 @@ +<% provide(:title, @subject) %> + + +<% if !@user.anonymous? %> +

Hi <%= @user.first_name %>, +

+<% end %> + +<% if @user.sent_take_plesson_email_times == 0 %> +

+ Now that you've enjoyed your first two lessons free, continue down the musical path you've started with your instructor. Schedule recurring weekly lessons for the best results and fastest progress, or schedule lessons one at a time. It's up to you. +

+

+ You can book your regular weekly or one-at-a-time lessons with your instructor at his/her profile page here: <%= @teacher.teacher_profile_url %>. +

+

+ If you have any trouble, please email us at support@jamkazam.com. Or you can call us at 877-376-8742. +

+ <% elsif @user.sent_take_plesson_email_times == 1 %> +

+ You're off to a great start with your music teacher. Don't lose momentum! +

+

+ You can book your regular weekly or one-at-a-time lessons with your instructor at his/her profile page here: <%= @teacher.teacher_profile_url %>. +

+

+ If you have any trouble, please email us at support@jamkazam.com. Or you can call us at 877-376-8742. +

+ <% else %> +

+ The best way - by far - to master your instrument and reach your musical goals is to have a great instructor - someone who can guide you, build the right foundation and technique from the beginning, inspire you, and make your practice pay off with the greatest return on your investment of time. If you want to play well, stay on the path with your instructor. +

+

+ You can book your regular weekly or one-at-a-time lessons with your instructor at his/her profile page here: <%= @teacher.teacher_profile_url %>. +

+

+ Or if you need to find a different instructor for any reason, here's an article on how to search for your ideal teacher: http://bit.ly/2kBPXBz. And here is an article that explains how to book your lesson: http://bit.ly/2k3qNMT. +

+

+ If you have any trouble, please email us at support@jamkazam.com. Or you can call us at 877-376-8742. +

+ <% end %> + +

Best Regards,
+ Team JamKazam

\ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/take_paid_lesson.text.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/take_paid_lesson.text.erb new file mode 100644 index 000000000..935699c18 --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/take_paid_lesson.text.erb @@ -0,0 +1,28 @@ +<% if !@user.anonymous? %> +Hi <%= @user.first_name %>, +<% end %> + +<% if @user.sent_take_plesson_email_times == 0 %> +Now that you've enjoyed your first two lessons free, continue down the musical path you've started with your instructor. Schedule recurring weekly lessons for the best results and fastest progress, or schedule lessons one at a time. It's up to you. + +You can book your regular weekly or one-at-a-time lessons with your instructor at his/her profile page here: <%= @teacher.teacher_profile_url %>. + +If you have any trouble, please email us at support@jamkazam.com. Or you can call us at 877-376-8742. +<% elsif @user.sent_take_plesson_email_times == 1 %> +You're off to a great start with your music teacher. Don't lose momentum! + +You can book your regular weekly or one-at-a-time lessons with your instructor at his/her profile page here: <%= @teacher.teacher_profile_url %>. + +If you have any trouble, please email us at support@jamkazam.com. Or you can call us at 877-376-8742. +<% else %> +The best way - by far - to master your instrument and reach your musical goals is to have a great instructor - someone who can guide you, build the right foundation and technique from the beginning, inspire you, and make your practice pay off with the greatest return on your investment of time. If you want to play well, stay on the path with your instructor. + +You can book your regular weekly or one-at-a-time lessons with your instructor at his/her profile page here: <%= @teacher.teacher_profile_url %>. + +Or if you need to find a different instructor for any reason, here's an article on how to search for your ideal teacher: http://bit.ly/2kBPXBz. And here is an article that explains how to book your lesson: http://bit.ly/2k3qNMT. + +If you have any trouble, please email us at support@jamkazam.com. Or you can call us at 877-376-8742. +<% end %> + +Best Regards, +Team JamKazam diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/take_second_free_lesson.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/take_second_free_lesson.html.erb new file mode 100644 index 000000000..bca989c40 --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/take_second_free_lesson.html.erb @@ -0,0 +1,29 @@ +<% provide(:title, @subject) %> + + +<% if !@user.anonymous? %> +

Hi <%= @user.first_name %>, +

+<% end %> + +<% if @user.sent_take_2nd_flesson_email_times == 0 %> +

We hope you had a great first music lesson with <%= @teacher.name %>. Schedule your second free lesson with this instructor now at this page: <%= @teacher.teacher_profile_url %>, and continue your musical journey!

+

+ If you have any trouble, please email us at support@jamkazam.com. Or you can call us at 877-376-8742. +

+<% elsif @user.sent_take_2nd_flesson_email_times == 1 %> +

Just a quick reminder to take advantage of your second free lesson, and book it today! Schedule your second lesson with the same instructor from your first lesson at this page: <%= @teacher.teacher_profile_url %>.

+

Or if you didn't love your instructor for any reason, you can search for a different instructor for your second free lesson. Here's the article on how to search for your ideal teacher: http://bit.ly/2kBPXBz. And here is an article that explains how to book your lesson: http://bit.ly/2k3qNMT.

+

+ If you have any trouble, please email us at support@jamkazam.com. Or you can call us at 877-376-8742. +

+<% else %> +

We're sending you one last reminder to book your second free lesson. Don't put it off to later and forget! A great instructor will make a huge difference in the progress you make in mastering your new instrument. You can book another lesson with your first instructor at his/her profile page here: <%= @teacher.teacher_profile_url %>.

+

Or if you didn't love your instructor for any reason, you can search for a different instructor for your second free lesson. Here's the article on how to search for your ideal teacher: http://bit.ly/2kBPXBz. And here is an article that explains how to book your lesson: http://bit.ly/2k3qNMT.

+

+ If you have any trouble, please email us at support@jamkazam.com. Or you can call us at 877-376-8742. +

+<% end %> + +

Best Regards,
+ Team JamKazam

\ No newline at end of file diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/take_second_free_lesson.text.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/take_second_free_lesson.text.erb new file mode 100644 index 000000000..115ee401a --- /dev/null +++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/take_second_free_lesson.text.erb @@ -0,0 +1,24 @@ +<% if !@user.anonymous? %> +Hi <%= @user.first_name %>, +<% end %> + +<% if @user.sent_take_2nd_flesson_email_times == 0 %> +We hope you had a great first music lesson with <%= @teacher.name %>. Schedule your second free lesson with this instructor now at this page: <%= @teacher.teacher_profile_url %>, and continue your musical journey! + +If you have any trouble, please email us at support@jamkazam.com. Or you can call us at 877-376-8742. +<% elsif @user.sent_take_2nd_flesson_email_times == 1 %> +Just a quick reminder to take advantage of your second free lesson, and book it today! Schedule your second lesson with the same instructor from your first lesson at this page: <%= @teacher.teacher_profile_url %>. + +Or if you didn't love your instructor for any reason, you can search for a different instructor for your second free lesson. Here's the article on how to search for your ideal teacher: http://bit.ly/2kBPXBz. And here is an article that explains how to book your lesson: http://bit.ly/2k3qNMT. + +If you have any trouble, please email us at support@jamkazam.com. Or you can call us at 877-376-8742. +<% else %> +We're sending you one last reminder to book your second free lesson. Don't put it off to later and forget! A great instructor will make a huge difference in the progress you make in mastering your new instrument. You can book another lesson with your first instructor at his/her profile page here: <%= @teacher.teacher_profile_url %>. + +Or if you didn't love your instructor for any reason, you can search for a different instructor for your second free lesson. Here's the article on how to search for your ideal teacher: http://bit.ly/2kBPXBz. And here is an article that explains how to book your lesson: http://bit.ly/2k3qNMT. + +If you have any trouble, please email us at support@jamkazam.com. Or you can call us at 877-376-8742. +<% end %> + +Best Regards, +Team JamKazam \ No newline at end of file diff --git a/ruby/lib/jam_ruby/models/anonymous_user.rb b/ruby/lib/jam_ruby/models/anonymous_user.rb index e32a230ea..9890a52ba 100644 --- a/ruby/lib/jam_ruby/models/anonymous_user.rb +++ b/ruby/lib/jam_ruby/models/anonymous_user.rb @@ -41,6 +41,10 @@ module JamRuby 0 end + def timezone + @cookies[:'browser.timezone'] + end + def free_jamtracks if has_redeemable_jamtrack 1 diff --git a/ruby/lib/jam_ruby/models/lesson_booking.rb b/ruby/lib/jam_ruby/models/lesson_booking.rb index ddd30c2b1..39e2fdfd1 100644 --- a/ruby/lib/jam_ruby/models/lesson_booking.rb +++ b/ruby/lib/jam_ruby/models/lesson_booking.rb @@ -737,7 +737,12 @@ module JamRuby self.autocanceling = true self.active = false self.status = STATUS_UNCONFIRMED - save + if save + if is_test_drive? + user.jamclass_credits = user.jamclass_credits + 1 + user.save(validate:false) + end + end self end @@ -840,7 +845,8 @@ module JamRuby if !user.has_test_drives? && !user.can_buy_test_drive? errors.add(:user, "have no remaining test drives") elsif teacher.has_booked_test_drive_with_student?(user) && !user.admin - errors.add(:user, "have an in-progress or successful TestDrive with this teacher already") + # we don't want to restrict this anymore. just let'em go to same teacher + # errors.add(:user, "have an in-progress or successful TestDrive with this teacher already") end end @@ -962,6 +968,7 @@ module JamRuby if lesson_booking.same_school lesson_booking.same_school_free = false # !user.school.education # non-education schools (music schools) are 'free' when school-on-school end + user.update_timezone(lesson_booking_slots[0].timezone) if lesson_booking_slots.length > 0 else lesson_booking.same_school = false lesson_booking.same_school_free = false @@ -975,7 +982,7 @@ module JamRuby end if lesson_booking_slots if lesson_booking.save - description = '' if description.nil? + description = '' if description.nil? msg = ChatMessage.create(user, lesson_booking.lesson_sessions[0], description, ChatMessage::CHANNEL_LESSON, nil, teacher, lesson_booking.lesson_sessions[0], 'Lesson Requested') end end diff --git a/ruby/lib/jam_ruby/models/lesson_booking_slot.rb b/ruby/lib/jam_ruby/models/lesson_booking_slot.rb index 0b190609f..74911ae5f 100644 --- a/ruby/lib/jam_ruby/models/lesson_booking_slot.rb +++ b/ruby/lib/jam_ruby/models/lesson_booking_slot.rb @@ -166,12 +166,14 @@ module JamRuby found ||= lesson_session.lesson_booking end - def pretty_scheduled_start(with_timezone = true) + def pretty_scheduled_start(with_timezone = true, user_tz = nil) start_time = scheduled_time(0) + tz_identifier = user_tz || self.timezone + begin - tz = TZInfo::Timezone.get(timezone) + tz = TZInfo::Timezone.get(tz_identifier) rescue Exception => e @@log.error("unable to find timezone=#{tz_identifier}, e=#{e}") end @@ -205,12 +207,14 @@ module JamRuby end end - def pretty_start_time(with_timezone = true) + def pretty_start_time(with_timezone = true, user_tz = nil) start_time = scheduled_time(0) + tz_identifier = user_tz || self.timezone + begin - tz = TZInfo::Timezone.get(timezone) + tz = TZInfo::Timezone.get(tz_identifier) rescue Exception => e @@log.error("unable to find timezone=#{tz_identifier}, e=#{e}") end diff --git a/ruby/lib/jam_ruby/models/lesson_session.rb b/ruby/lib/jam_ruby/models/lesson_session.rb index 3251695d8..fe9a00f4f 100644 --- a/ruby/lib/jam_ruby/models/lesson_session.rb +++ b/ruby/lib/jam_ruby/models/lesson_session.rb @@ -94,11 +94,11 @@ module JamRuby end def teacher_distribution - teacher_distributions.where(education:false).where('retailer_id is null').first + teacher_distributions.where(education: false).where('retailer_id is null').first end def education_distribution - teacher_distributions.where(education:true).first + teacher_distributions.where(education: true).first end def retailer_distribution @@ -131,6 +131,7 @@ module JamRuby complete_sessions remind_counters remind_counters_recurring + onboarding_email_reminders end def self.minutely_check @@ -144,9 +145,9 @@ module JamRuby .where("? > (COALESCE(lesson_sessions.sent_counter_reminder_at, lesson_sessions.countered_at, lesson_bookings.sent_notices_at) + (INTERVAL '24 hours'))", Time.now).each do |music_session| lesson_session = music_session.lesson_session if lesson_session.student_last_proposed? - relevant_time = [lesson_session.countered_at, lesson_session.lesson_booking.sent_notices_at].find{|x|!x.nil?} + relevant_time = [lesson_session.countered_at, lesson_session.lesson_booking.sent_notices_at].find { |x| !x.nil? } - UserMailer.student_no_comm_other(lesson_session.lesson_booking, relevant_time < Time.now - 3.days).deliver_now + UserMailer.student_no_comm_other(lesson_session.lesson_booking, relevant_time < 3.days.ago).deliver_now UserMailer.teacher_counter_reminder(lesson_session).deliver_now else UserMailer.teacher_no_comm_other(lesson_session.lesson_booking).deliver_now @@ -159,7 +160,7 @@ module JamRuby def self.remind_counters_recurring MusicSession.joins(lesson_session: :lesson_booking) - .where("lesson_bookings.status = ? AND lesson_bookings.sent_counter_reminder = false AND lesson_bookings.recurring = TRUE",LessonBooking::STATUS_COUNTERED) + .where("lesson_bookings.status = ? AND lesson_bookings.sent_counter_reminder = false AND lesson_bookings.recurring = TRUE", LessonBooking::STATUS_COUNTERED) .where("? > (COALESCE(lesson_sessions.countered_at, lesson_bookings.sent_notices_at) + (INTERVAL '24 hours'))", Time.now).each do |music_session| lesson_session = music_session.lesson_session if lesson_session.student_last_proposed? @@ -172,6 +173,51 @@ module JamRuby end end + def self.remindable_onboarding_users + User.where('first_onboarding_paid_lesson_at IS NULL AND onboarding_onboarded_at IS NOT NULL') + .where('stuck_take_flesson = FALSE and stuck_take_2nd_flesson = FALSE and stuck_take_plesson = FALSE') + end + + def self.onboarding_email_reminders + remindable_onboarding_users.each do |user| + if user.second_onboarding_free_lesson_at && user.sent_take_plesson_email_times < 4 + + last_sent = user.sent_take_plesson_email_at || user.second_onboarding_free_lesson_at + if last_sent < 2.days.ago + if user.sent_take_plesson_email_times == 3 + User.update_all(id: user.id, stuck_take_plesson: true) + else + UserMailer.take_paid_lesson(user).deliver_now + User.update_all(id: user.id, sent_take_plesson_email_times: user.sent_take_plesson_email_times + 1, sent_take_plesson_email_at: Time.now) + end + end + + elsif user.first_onboarding_free_lesson_at && user.sent_take_2nd_flesson_email_times < 4 + last_sent = user.sent_take_2nd_flesson_email_at || user.first_onboarding_free_lesson_at + if last_sent < 2.days.ago + if user.sent_take_2nd_flesson_email_times == 3 + User.update_all(id: user.id, stuck_take_2nd_flesson: true) + else + UserMailer.take_second_free_lesson(user).deliver_now + User.update_all(id: user.id, sent_take_2nd_flesson_email_times: user.sent_take_2nd_flesson_email_times + 1, sent_take_2nd_flesson_email_at: Time.now) + end + end + + elsif user.sent_take_flesson_email_times < 4 + last_sent = user.sent_take_flesson_email_at || user.onboarding_onboarded_at + if last_sent < 2.days.ago + if user.sent_take_flesson_email_times == 3 + User.update_all(id: user.id, stuck_take_flesson: true) + else + UserMailer.take_first_free_lesson(user).deliver_now + User.update_all(id: user.id, sent_take_flesson_email_times: user.sent_take_flesson_email_times + 1, sent_take_flesson_email_at: Time.now) + end + end + end + end + end + + # 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') + (INTERVAL '1 minutes' * (duration))", Time.now).each do |music_session| @@ -319,15 +365,16 @@ module JamRuby if student.first_onboarding_free_lesson_at.nil? student.first_onboarding_free_lesson_at = Time.now student.save + elsif student.second_onboarding_free_lesson_at.nil? + student.second_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]) @@ -399,6 +446,7 @@ module JamRuby student end end + def session_completed LessonSession.transaction do self.lock! @@ -473,7 +521,7 @@ module JamRuby if !sent_notices if success - teacher_distributions.update_all(ready:true) # possibly there are 0 distributions on this lesson + teacher_distributions.update_all(ready: true) # possibly there are 0 distributions on this lesson student.test_drive_succeeded(self) else student.test_drive_failed(self) @@ -984,6 +1032,8 @@ module JamRuby #end if self.save + proposer.update_timezone(slot.timezone) if proposer + #if update_all && !lesson_booking.counter(self, proposer, slot) if !lesson_booking.counter(self, proposer, slot) response = lesson_booking @@ -1135,6 +1185,7 @@ module JamRuby 'TBD' end end + def timed_description if is_test_drive? "TestDrive session with #{self.lesson_booking.student.name} on #{time_for_admin}" @@ -1149,11 +1200,11 @@ module JamRuby def cancel_by_admin self.cancel({ - canceler: nil, - canceled_by_admin: true, - message: 'Canceled by JamKazam administrator', - update_all: false - }) + canceler: nil, + canceled_by_admin: true, + message: 'Canceled by JamKazam administrator', + update_all: false + }) end def intervened @@ -1176,7 +1227,7 @@ module JamRuby end def last_response_time - [countered_at, lesson_booking.sent_notices_at].find{|x|!x.nil?} + [countered_at, lesson_booking.sent_notices_at].find { |x| !x.nil? } end def stripe_description(lesson_booking) diff --git a/ruby/lib/jam_ruby/models/music_session.rb b/ruby/lib/jam_ruby/models/music_session.rb index d53ba7444..fb4a37d30 100644 --- a/ruby/lib/jam_ruby/models/music_session.rb +++ b/ruby/lib/jam_ruby/models/music_session.rb @@ -1215,7 +1215,7 @@ SQL duration end - def pretty_scheduled_start(with_timezone = true, shorter = false) + def pretty_scheduled_start(with_timezone = true, shorter = false, user_tz = nil) if scheduled_start && scheduled_duration start_time = scheduled_start @@ -1224,6 +1224,9 @@ SQL tz_identifier, tz_display = MusicSession.split_timezone(timezone) short_tz = 'GMT' + if user_tz + tz_identifier = user_tz + end begin tz = TZInfo::Timezone.get(tz_identifier) rescue Exception => e diff --git a/ruby/lib/jam_ruby/models/teacher.rb b/ruby/lib/jam_ruby/models/teacher.rb index 09515a4ba..0da61ccfd 100644 --- a/ruby/lib/jam_ruby/models/teacher.rb +++ b/ruby/lib/jam_ruby/models/teacher.rb @@ -148,7 +148,7 @@ module JamRuby # Real teachers who are marked as top. # Real teachers who are not marked as top. # Phantom teachers. - query = query.order("top_rated DESC, phantom ASC") + query = query.order("phantom ASC, random_order ASC") current_page = params[:page].nil? ? 1 : params[:page].to_i next_page = current_page + 1 @@ -165,6 +165,10 @@ module JamRuby end end + def self.randomize_order + self.connection.execute("update teachers set random_order = sub.row_number * random() * 1000 from (select id, row_number() over () from teachers) as sub ;") + end + def self.save_teacher(user, params) teacher = nil Teacher.transaction do diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index baf599bd4..76971c1c4 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -38,7 +38,7 @@ module JamRuby ONBOARDING_STATUS_ONBOARDED = "Onboarded" ONBOARDING_STATUS_LOST = "Lost" ONBOARDING_STATUS_ESCALATED = "Escalated" - ONBOARDING_STATUS_FREE_LESSON = "Free Lesson Taken" + ONBOARDING_STATUS_FREE_LESSON = "One 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" @@ -136,7 +136,7 @@ module JamRuby # notifications has_many :notifications, :class_name => "JamRuby::Notification", :foreign_key => "target_user_id" - + # chats has_many :chats, :class_name => "JamRuby::ChatMessage", :foreign_key => "user_id" @@ -328,16 +328,50 @@ module JamRuby onboarding_email_5_sent_at_changed? || first_onboarding_free_lesson_at_changed? || first_onboarding_paid_lesson_at_changed? || + second_onboarding_free_lesson_at? || onboarding_onboarded_at - User.where(id: self.id).update_all(onboarding_status: self.computed_onboarding_status) + + updates = {onboarding_status: self.computed_onboarding_status} + + if first_onboarding_free_lesson_at_changed? || first_onboarding_paid_lesson_at_changed? || second_onboarding_free_lesson_at? + updates[:stuck_take_flesson] = false + updates[:stuck_take_2nd_flesson] = false + updates[:stuck_take_plesson] = false + end + + if updates[:onboarding_status] == ONBOARDING_STATUS_ONBOARDED || updates[:onboarding_status] == ONBOARDING_STATUS_LOST || updates[:onboarding_status] == ONBOARDING_STATUS_ESCALATED + updates[:send_onboarding_survey] = true + end + + User.where(id: self.id).update_all(updates) end end + + def self.hourly_check + send_onboarding_surveys + end + + def self.send_onboarding_surveys + User.where(send_onboarding_survey: true, sent_onboarding_survey_at: nil).each do |user| + UserMailer.onboarding_survey(user).deliver_now + User.where(id: user.id).update_all(sent_onboarding_survey_at: Time.now) + end + end + def update_teacher_pct if teacher teacher.update_profile_pct end end + def is_waiting_onboarding + ONBOARDING_STATUS_UNASSIGNED + end + + def is_onboarding + ONBOARDING_STATUS_ASSIGNED || ONBOARDING_STATUS_ESCALATED || ONBOARDING_STATUS_EMAILED + end + def user_progression_fields @user_progression_fields ||= Set.new ["first_downloaded_client_at", "first_ran_client_at", "first_music_session_at", "first_real_music_session_at", "first_good_music_session_at", "first_certified_gear_at", "first_invited_at", "first_friended_at", "first_recording_at", "first_social_promoted_at", "first_played_jamtrack_at"] end @@ -350,6 +384,18 @@ module JamRuby end end + def update_timezone(timezone) + if timezone.nil? + return + + end + begin + TZInfo::Timezone.get(timezone) + User.where(id: self.id).update_all(timezone: timezone) + rescue Exception => e + @@log.error("unable to find timezone=#{timezone}, e=#{e}") + end + end def has_any_free_jamtracks has_redeemable_jamtrack || gifted_jamtracks > 0 end @@ -1211,6 +1257,7 @@ module JamRuby origin = options[:origin] test_drive_package_details = options[:test_drive_package] under_13 = options[:under_13] + timezone = options[:timezone] test_drive_package = TestDrivePackage.find_by_name(test_drive_package_details[:name]) if test_drive_package_details @@ -1540,6 +1587,8 @@ module JamRuby end end end + + user.update_timezone(timezone) user.reload if user.id # gift card adding gifted_jamtracks doesn't reflect here until reload user end @@ -1661,6 +1710,14 @@ module JamRuby "#{dir}/#{ERB::Util.url_encode(file)}" end + def permanently_delete + original_email = self.email + + AdminMailer.ugly({to: original_email, cc: 'support@jamkazam.com', subject:'Your account has been deleted!', body: "This will be the last email you receive from JamKazam.\n\nRegards,\nTeam JamKazam"}).deliver_now + + User.where(id: self.id).update_all(encrypted_password: SecureRandom.uuid, email: "deleted+#{SecureRandom.uuid}@jamkazam.com", subscribe_email: false, remember_token: SecureRandom.uuid, deleted: true) + end + def update_avatar(original_fpfile, cropped_fpfile, cropped_large_fpfile, crop_selection, aws_bucket) self.updating_avatar = true @@ -2479,6 +2536,35 @@ module JamRuby self.save! end + def mark_sent_paid_lesson + self.stuck_take_plesson = false + self.sent_admin_take_plesson_email_at = Time.now + self.save! + end + + def mark_sent_2nd_free_lesson + self.stuck_take_2nd_flesson = false + self.sent_admin_take_2nd_flesson_email_at = Time.now + self.save! + end + + def mark_sent_1st_free_lesson + self.stuck_take_flesson = false + self.sent_admin_take_flesson_email_at = Time.now + self.save! + end + + def mark_onboarded + self.onboarding_onboarded_at = Time.now + self.save! + end + + def mark_lost(lost_reason = LOST_REASON_OTHER) + self.onboarding_lost_at = Time.now + self.onboarding_lost_reason = lost_reason + self.save! + end + def has_booked_with_student?(student, since_at = nil) LessonBooking.engaged_bookings(student, self, since_at).count > 0 end @@ -2494,7 +2580,7 @@ module JamRuby def computed_onboarding_status if first_onboarding_paid_lesson_at ONBOARDING_STATUS_PAID_LESSON - elsif first_onboarding_free_lesson_at + elsif second_onboarding_free_lesson_at || first_onboarding_free_lesson_at ONBOARDING_STATUS_FREE_LESSON elsif onboarding_onboarded_at ONBOARDING_STATUS_ONBOARDED diff --git a/ruby/lib/jam_ruby/resque/scheduled/daily_job.rb b/ruby/lib/jam_ruby/resque/scheduled/daily_job.rb index 286285b4b..6e39d635e 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/daily_job.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/daily_job.rb @@ -8,6 +8,8 @@ module JamRuby def self.perform @@log.debug("waking up") + Teacher.randomize_order + bounced_emails calendar_manager = CalendarManager.new @@ -15,6 +17,7 @@ module JamRuby MusicSession.cleanup_old_sessions + @@log.debug("done") end diff --git a/ruby/lib/jam_ruby/resque/scheduled/hourly_job.rb b/ruby/lib/jam_ruby/resque/scheduled/hourly_job.rb index c603a1f51..d2d64000c 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/hourly_job.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/hourly_job.rb @@ -11,7 +11,7 @@ module JamRuby LessonBooking.hourly_check LessonSession.hourly_check TeacherPayment.hourly_check - + User.hourly_check @@log.debug("done") end diff --git a/ruby/spec/jam_ruby/models/lesson_session_spec.rb b/ruby/spec/jam_ruby/models/lesson_session_spec.rb index 9c4b984bc..8ab0a4e6f 100644 --- a/ruby/spec/jam_ruby/models/lesson_session_spec.rb +++ b/ruby/spec/jam_ruby/models/lesson_session_spec.rb @@ -9,6 +9,8 @@ describe LessonSession do let(:lesson_booking) { b = LessonBooking.book_normal(user, teacher, [slot1, slot2], "Hey I've heard of you before.", false, LessonBooking::PAYMENT_STYLE_SINGLE, 60); b.card_presumed_ok = true; b.save!; b } let(:lesson_session) { lesson_booking.lesson_sessions[0] } + let(:td_lesson_booking) { b = LessonBooking.book_test_drive(user, teacher, [slot1, slot2], "Hey I've heard of you before."); b.card_presumed_ok = true; b.save!; b } + let(:td_lesson_session) { td_lesson_booking.lesson_sessions[0] } describe "counter" do describe "recurring" do @@ -215,6 +217,21 @@ describe LessonSession do end describe "autocancel" do + it "returns credit" do + + jamclass_credits = user.jamclass_credits + + td_lesson_session.status.should eql LessonSession::STATUS_REQUESTED + + Timecop.travel(Date.today + 10) + + td_lesson_session.autocancel + td_lesson_session.reload + td_lesson_session.status.should eql LessonSession::STATUS_UNCONFIRMED + td_lesson_session.lesson_booking.status.should eql LessonSession::STATUS_UNCONFIRMED + user.reload + user.jamclass_credits.should eql (jamclass_credits + 1) + end it "can't autocancel in the past" do lesson_session.status.should eql LessonSession::STATUS_REQUESTED @@ -271,19 +288,6 @@ describe LessonSession do slow[1].should eql lesson_session1 end end - describe "least_time_left" do - it "sorts correctly" do - lesson_session1 = normal_lesson(user, teacher, {counter: true, counterer: user}) - lesson_session2 = normal_lesson(user, teacher, {counter: true, counterer: teacher}) # this shouldn't show up - Timecop.travel(Date.today - 5) - lesson_session3 = normal_lesson(user, teacher, {}) - - slow = LessonSession.unscoped.least_time_left - slow.count.should eql 2 - slow[0].should eql lesson_session1 - slow[1].should eql lesson_session3 - end - end describe "remind_counters" do it "finds old requested and pokes teacher" do @@ -472,9 +476,9 @@ describe LessonSession do first_lesson.is_canceled?.should be true second_lesson.is_canceled?.should be true booking.is_canceled?.should be true - first_lesson.student_short_canceled.should be true first_lesson.student_canceled.should be true first_lesson.teacher_canceled.should be false + first_lesson.student_short_canceled.should be true second_lesson.student_short_canceled.should be false second_lesson.student_canceled.should be true second_lesson.teacher_canceled.should be false @@ -517,6 +521,226 @@ describe LessonSession do end end + describe "remindable_onboarding_users" do + it "works" do + LessonSession.remindable_onboarding_users.count.should eql 0 + + user.onboarding_onboarded_at = Time.now + user.first_onboarding_paid_lesson_at = nil + user.save! + + LessonSession.remindable_onboarding_users.count.should eql 1 + + user.sent_take_plesson_email_times = 3 + user.sent_take_2nd_flesson_email_times = 3 + user.sent_take_plesson_email_times = 3 + user.save! + + LessonSession.remindable_onboarding_users.count.should eql 0 + end + end + + describe "onboarding_email_reminders" do + it "works" do + user.onboarding_onboarded_at = Time.now + user.first_onboarding_paid_lesson_at = nil + user.save! + + mailer = mock + UserMailer.deliveries.clear + + LessonSession.onboarding_email_reminders + + Timecop.travel(Date.today + 3) + mailer.should_receive(:deliver_now).once + UserMailer.should_receive(:take_first_free_lesson).and_return(mailer) + LessonSession.onboarding_email_reminders + #UserMailer.deliveries.count.should eql 1 + user.reload + user.sent_take_flesson_email_times.should eql 1 + user.sent_take_2nd_flesson_email_times.should eql 0 + user.sent_take_plesson_email_times.should eql 0 + user.stuck_take_flesson.should be_false + user.stuck_take_2nd_flesson.should be_false + user.stuck_take_plesson.should be_false + #RSpec::Mocks.space.proxy_for(mailer).reset + #RSpec::Mocks.space.proxy_for(UserMailer).reset + UserMailer.deliveries.clear + + LessonSession.onboarding_email_reminders + user.reload + user.sent_take_flesson_email_times.should eql 1 + user.sent_take_2nd_flesson_email_times.should eql 0 + user.sent_take_plesson_email_times.should eql 0 + user.stuck_take_flesson.should be_false + user.stuck_take_2nd_flesson.should be_false + user.stuck_take_plesson.should be_false + #RSpec::Mocks.space.proxy_for(mailer).reset + #RSpec::Mocks.space.proxy_for(UserMailer).reset + UserMailer.deliveries.clear + + Timecop.travel(Date.today + 3) + mailer.should_receive(:deliver_now).once + UserMailer.should_receive(:take_first_free_lesson).and_return(mailer) + LessonSession.onboarding_email_reminders + user.reload + user.sent_take_flesson_email_times.should eql 2 + user.sent_take_2nd_flesson_email_times.should eql 0 + user.sent_take_plesson_email_times.should eql 0 + user.stuck_take_flesson.should be_false + user.stuck_take_2nd_flesson.should be_false + user.stuck_take_plesson.should be_false + #RSpec::Mocks.space.proxy_for(mailer).reset + #RSpec::Mocks.space.proxy_for(UserMailer).reset + UserMailer.deliveries.clear + + Timecop.travel(Date.today + 3) + mailer.should_receive(:deliver_now).once + UserMailer.should_receive(:take_first_free_lesson).and_return(mailer) + LessonSession.onboarding_email_reminders + user.reload + user.sent_take_flesson_email_times.should eql 3 + user.sent_take_2nd_flesson_email_times.should eql 0 + user.sent_take_plesson_email_times.should eql 0 + user.stuck_take_flesson.should be_false + user.stuck_take_2nd_flesson.should be_false + user.stuck_take_plesson.should be_false + #RSpec::Mocks.space.proxy_for(mailer).reset + #RSpec::Mocks.space.proxy_for(UserMailer).reset + UserMailer.deliveries.clear + + Timecop.travel(Date.today + 3) + LessonSession.onboarding_email_reminders + user.reload + user.sent_take_flesson_email_times.should eql 3 + user.sent_take_2nd_flesson_email_times.should eql 0 + user.sent_take_plesson_email_times.should eql 0 + user.stuck_take_flesson.should be_true + user.stuck_take_2nd_flesson.should be_false + user.stuck_take_plesson.should be_false + #RSpec::Mocks.space.proxy_for(mailer).reset + #RSpec::Mocks.space.proxy_for(UserMailer).reset + UserMailer.deliveries.clear + + user.first_onboarding_free_lesson_at = Time.now + user.save! + + LessonSession.onboarding_email_reminders + user.reload + user.sent_take_flesson_email_times.should eql 3 + user.sent_take_2nd_flesson_email_times.should eql 0 + user.sent_take_plesson_email_times.should eql 0 + user.stuck_take_flesson.should be_false + user.stuck_take_2nd_flesson.should be_false + user.stuck_take_plesson.should be_false + + Timecop.travel(Date.today + 3) + mailer.should_receive(:deliver_now).once + UserMailer.should_receive(:take_second_free_lesson).and_return(mailer) + LessonSession.onboarding_email_reminders + user.reload + user.sent_take_flesson_email_times.should eql 3 + user.sent_take_2nd_flesson_email_times.should eql 1 + user.sent_take_plesson_email_times.should eql 0 + user.stuck_take_flesson.should be_false + user.stuck_take_2nd_flesson.should be_false + user.stuck_take_plesson.should be_false + + Timecop.travel(Date.today + 3) + mailer.should_receive(:deliver_now).once + UserMailer.should_receive(:take_second_free_lesson).and_return(mailer) + LessonSession.onboarding_email_reminders + user.reload + user.sent_take_flesson_email_times.should eql 3 + user.sent_take_2nd_flesson_email_times.should eql 2 + user.sent_take_plesson_email_times.should eql 0 + user.stuck_take_flesson.should be_false + user.stuck_take_2nd_flesson.should be_false + user.stuck_take_plesson.should be_false + + Timecop.travel(Date.today + 3) + mailer.should_receive(:deliver_now).once + UserMailer.should_receive(:take_second_free_lesson).and_return(mailer) + LessonSession.onboarding_email_reminders + user.reload + user.sent_take_flesson_email_times.should eql 3 + user.sent_take_2nd_flesson_email_times.should eql 3 + user.sent_take_plesson_email_times.should eql 0 + user.stuck_take_flesson.should be_false + user.stuck_take_2nd_flesson.should be_false + user.stuck_take_plesson.should be_false + + Timecop.travel(Date.today + 3) + LessonSession.onboarding_email_reminders + user.reload + user.sent_take_flesson_email_times.should eql 3 + user.sent_take_2nd_flesson_email_times.should eql 3 + user.sent_take_plesson_email_times.should eql 0 + user.stuck_take_flesson.should be_false + user.stuck_take_2nd_flesson.should be_true + user.stuck_take_plesson.should be_false + + user.second_onboarding_free_lesson_at = Time.now + user.save! + + LessonSession.onboarding_email_reminders + user.reload + user.sent_take_flesson_email_times.should eql 3 + user.sent_take_2nd_flesson_email_times.should eql 3 + user.sent_take_plesson_email_times.should eql 0 + user.stuck_take_flesson.should be_false + user.stuck_take_2nd_flesson.should be_false + user.stuck_take_plesson.should be_false + + Timecop.travel(Date.today + 3) + mailer.should_receive(:deliver_now).once + UserMailer.should_receive(:take_paid_lesson).and_return(mailer) + LessonSession.onboarding_email_reminders + user.reload + user.sent_take_flesson_email_times.should eql 3 + user.sent_take_2nd_flesson_email_times.should eql 3 + user.sent_take_plesson_email_times.should eql 1 + user.stuck_take_flesson.should be_false + user.stuck_take_2nd_flesson.should be_false + user.stuck_take_plesson.should be_false + + Timecop.travel(Date.today + 3) + mailer.should_receive(:deliver_now).once + UserMailer.should_receive(:take_paid_lesson).and_return(mailer) + LessonSession.onboarding_email_reminders + user.reload + user.sent_take_flesson_email_times.should eql 3 + user.sent_take_2nd_flesson_email_times.should eql 3 + user.sent_take_plesson_email_times.should eql 2 + user.stuck_take_flesson.should be_false + user.stuck_take_2nd_flesson.should be_false + user.stuck_take_plesson.should be_false + + Timecop.travel(Date.today + 3) + mailer.should_receive(:deliver_now).once + UserMailer.should_receive(:take_paid_lesson).and_return(mailer) + LessonSession.onboarding_email_reminders + user.reload + user.sent_take_flesson_email_times.should eql 3 + user.sent_take_2nd_flesson_email_times.should eql 3 + user.sent_take_plesson_email_times.should eql 3 + user.stuck_take_flesson.should be_false + user.stuck_take_2nd_flesson.should be_false + user.stuck_take_plesson.should be_false + + Timecop.travel(Date.today + 3) + LessonSession.onboarding_email_reminders + user.reload + user.sent_take_flesson_email_times.should eql 3 + user.sent_take_2nd_flesson_email_times.should eql 3 + user.sent_take_plesson_email_times.should eql 3 + user.stuck_take_flesson.should be_false + user.stuck_take_2nd_flesson.should be_false + user.stuck_take_plesson.should be_true + + end + end + describe "index" do it "finds single lesson as student" do diff --git a/ruby/spec/jam_ruby/models/teacher_spec.rb b/ruby/spec/jam_ruby/models/teacher_spec.rb index 71c697063..9c7e59878 100644 --- a/ruby/spec/jam_ruby/models/teacher_spec.rb +++ b/ruby/spec/jam_ruby/models/teacher_spec.rb @@ -28,18 +28,18 @@ describe Teacher do teacher2 = FactoryGirl.create(:teacher, ready_for_session_at: Time.now) - teacher2.top_rated = false + teacher2.random_order = 1 teacher2.save! teacher1 = FactoryGirl.create(:teacher, ready_for_session_at: Time.now) - teacher1.top_rated = true + teacher1.random_order = 2 teacher1.save! query = Teacher.index(nil, {})[:query] query.count.should eql 3 - query[0].should eq(teacher1.user) - query[1].should eq(teacher2.user) + query[0].should eq(teacher2.user) + query[1].should eq(teacher1.user) query[2].should eq(teacher3.user) end @@ -228,8 +228,8 @@ describe Teacher do end it "student only sees guitar center teachers" do - teacher = FactoryGirl.create(:teacher, ready_for_session_at: Time.now) - gc_teacher = FactoryGirl.create(:teacher, school: @gc_school, ready_for_session_at: Time.now) + teacher = FactoryGirl.create(:teacher, ready_for_session_at: Time.now, random_order: 1) + gc_teacher = FactoryGirl.create(:teacher, school: @gc_school, ready_for_session_at: Time.now, random_order: 2) # TODO: perhaps GC teachers should not come back to non-GC users. Not sure yet. query = Teacher.index(user, {})[:query] diff --git a/ruby/spec/jam_ruby/models/user_spec.rb b/ruby/spec/jam_ruby/models/user_spec.rb index 3d3bfe62f..e88b5c3ce 100644 --- a/ruby/spec/jam_ruby/models/user_spec.rb +++ b/ruby/spec/jam_ruby/models/user_spec.rb @@ -931,6 +931,78 @@ describe User do end end end + describe "onboarding_status" do + let(:user) {FactoryGirl.create(:user)} + + it "onboarded" do + user.send_onboarding_survey.should be_false + user.onboarding_onboarded_at = Time.now + user.save! + user.send_onboarding_survey.should be_true + user.onboarding_status = User::ONBOARDING_STATUS_ONBOARDED + end + + it "lost" do + user.send_onboarding_survey.should be_false + user.onboarding_lost_reason = User::LOST_REASON_NO_VIDEO_STREAM + user.save! + user.send_onboarding_survey.should be_true + user.onboarding_status = User::ONBOARDING_STATUS_LOST + end + + it "escalated" do + user.send_onboarding_survey.should be_false + user.onboarding_escalation_reason = User::ESCALATION_REASON_NO_VIDEO_STREAM + user.save! + user.send_onboarding_survey.should be_true + user.onboarding_status = User::ONBOARDING_STATUS_ESCALATED + end + + it "taken free lesson" do + user.send_onboarding_survey.should be_false + user.first_onboarding_free_lesson_at = Time.now + user.save! + user.send_onboarding_survey.should be_false + user.onboarding_status = User::ONBOARDING_STATUS_FREE_LESSON + end + + it "paid lesson" do + user.send_onboarding_survey.should be_false + user.first_onboarding_paid_lesson_at = Time.now + user.save! + user.send_onboarding_survey.should be_false + user.onboarding_status = User::ONBOARDING_STATUS_PAID_LESSON + end + + it "assigned" do + user.send_onboarding_survey.should be_false + user.onboarder = FactoryGirl.create(:user, is_onboarder:true) + user.save! + user.send_onboarding_survey.should be_false + user.onboarding_status = User::ONBOARDING_STATUS_ASSIGNED + end + + end + describe "send_onboarding_surveys" do + let(:user) {FactoryGirl.create(:user)} + + it "works" do + UserMailer.deliveries.clear + user.send_onboarding_survey = true + user.save! + + User.send_onboarding_surveys + + UserMailer.deliveries.count.should eql 1 + user.reload + user.send_onboarding_survey.should be_true + user.sent_onboarding_survey_at.should_not be_nil + + UserMailer.deliveries.clear + User.send_onboarding_surveys + UserMailer.deliveries.count.should eql 0 + end + end =begin describe "update avatar" do diff --git a/ruby/spec/mailers/render_emails_spec.rb b/ruby/spec/mailers/render_emails_spec.rb index 7db4da562..9ec2f6783 100644 --- a/ruby/spec/mailers/render_emails_spec.rb +++ b/ruby/spec/mailers/render_emails_spec.rb @@ -210,6 +210,119 @@ describe "RenderMailers", :slow => true do UserMailer.deliveries.clear UserMailer.student_no_comm_other(lesson.lesson_booking, true).deliver_now end + + it "take_first_free_lesson 1" do + @filename = "take_first_free_lesson_1" + + user.sent_take_flesson_email_times = 0 + user.save! + + UserMailer.deliveries.clear + UserMailer.take_first_free_lesson(user).deliver_now + end + + it "take_first_free_lesson 2" do + @filename = "take_first_free_lesson_2" + + user.sent_take_flesson_email_times = 1 + user.save! + + UserMailer.deliveries.clear + UserMailer.take_first_free_lesson(user).deliver_now + end + + it "take_first_free_lesson 3" do + @filename = "take_first_free_lesson_3" + + user.sent_take_flesson_email_times = 2 + user.save! + + UserMailer.deliveries.clear + UserMailer.take_first_free_lesson(user).deliver_now + end + + it "take_second_free_lesson 1" do + @filename = "take_second_free_lesson_1" + + user.sent_take_2nd_flesson_email_times = 0 + user.save! + + lesson = testdrive_lesson(user, teacher) + + + UserMailer.deliveries.clear + UserMailer.take_second_free_lesson(user).deliver_now + end + + it "take_second_free_lesson 2" do + @filename = "take_second_free_lesson_2" + + user.sent_take_2nd_flesson_email_times = 1 + user.save! + + lesson = testdrive_lesson(user, teacher) + + UserMailer.deliveries.clear + UserMailer.take_second_free_lesson(user).deliver_now + end + + it "take_second_free_lesson 3" do + @filename = "take_second_free_lesson_3" + + user.sent_take_2nd_flesson_email_times = 2 + user.save! + + lesson = testdrive_lesson(user, teacher) + + UserMailer.deliveries.clear + UserMailer.take_second_free_lesson(user).deliver_now + end + + it "take_paid_lesson 1" do + @filename = "take_paid_lesson_1" + + user.sent_take_plesson_email_times = 0 + user.save! + + lesson = testdrive_lesson(user, teacher) + lesson = testdrive_lesson(user, teacher) + + + UserMailer.deliveries.clear + UserMailer.take_paid_lesson(user).deliver_now + end + + it "take_paid_lesson 2" do + @filename = "take_paid_lesson_2" + + user.sent_take_plesson_email_times = 1 + user.save! + + lesson = testdrive_lesson(user, teacher) + lesson = testdrive_lesson(user, teacher) + + UserMailer.deliveries.clear + UserMailer.take_paid_lesson(user).deliver_now + end + + it "take_paid_lesson 3" do + @filename = "take_paid_lesson_3" + + user.sent_take_plesson_email_times = 2 + user.save! + + lesson = testdrive_lesson(user, teacher) + lesson = testdrive_lesson(user, teacher) + + UserMailer.deliveries.clear + UserMailer.take_paid_lesson(user).deliver_now + end + + it "onboarding_survey" do + @filename = "onboarding_survey" + UserMailer.deliveries.clear + UserMailer.onboarding_survey(user).deliver_now + end end end diff --git a/web/app/assets/javascripts/basic/basic.js b/web/app/assets/javascripts/basic/basic.js index 331325c3c..c36d303b5 100644 --- a/web/app/assets/javascripts/basic/basic.js +++ b/web/app/assets/javascripts/basic/basic.js @@ -6,6 +6,7 @@ //= require jquery.monkeypatch //= require jquery_ujs //= require jquery.cookie +//= require jstz //= require AAC_underscore //= require AAA_Log //= require globals diff --git a/web/app/assets/javascripts/everywhere/everywhere.js b/web/app/assets/javascripts/everywhere/everywhere.js index f815cb65c..016eec279 100644 --- a/web/app/assets/javascripts/everywhere/everywhere.js +++ b/web/app/assets/javascripts/everywhere/everywhere.js @@ -45,8 +45,13 @@ trackScreenChanges(); + setTimezone() }) + function setTimezone() { + $.cookie("browser.timezone", window.jstz.determine().name(), { expires: 365, path: '/' }); + } + $(document).on('JAMKAZAM_READY', function() { // this event is fired when context.app is initialized @@ -99,6 +104,10 @@ function initializeDialogs(app) { + if(!JK.Banner) { + // we don't use dialogs everywhere (yes, ugly) + return + } var backendAlerts = new JK.BackendAlerts(app); backendAlerts.initialize(); diff --git a/web/app/assets/javascripts/ftue.js b/web/app/assets/javascripts/ftue.js index ec8d37e4e..54ee2c1a0 100644 --- a/web/app/assets/javascripts/ftue.js +++ b/web/app/assets/javascripts/ftue.js @@ -495,7 +495,7 @@ */ function loadAudioDrivers() { var drivers = context.jamClient.FTUEGetDevices(false); - var chatDrivers = jamClient.FTUEGetChatInputs(); + var chatDrivers = jamClient.FTUEGetChatInputs(false, false); var optionsHtml = ''; var chatOptionsHtml = ''; diff --git a/web/app/assets/javascripts/landing/landing.js b/web/app/assets/javascripts/landing/landing.js index 0410ed9c3..d44c8eb66 100644 --- a/web/app/assets/javascripts/landing/landing.js +++ b/web/app/assets/javascripts/landing/landing.js @@ -25,6 +25,7 @@ //= require jquery.manageVsts //= require jquery.lessonSessionActions //= require jquery.jamblasterOptions +//= require jstz //= require ResizeSensor //= require AAA_Log //= require AAC_underscore diff --git a/web/app/assets/javascripts/react-components/AccountOnboarderScreen.js.jsx.coffee b/web/app/assets/javascripts/react-components/AccountOnboarderScreen.js.jsx.coffee index d9a45305a..ca1cc2083 100644 --- a/web/app/assets/javascripts/react-components/AccountOnboarderScreen.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/AccountOnboarderScreen.js.jsx.coffee @@ -149,8 +149,14 @@ profileUtils = context.JK.ProfileUtils value = $target.val() options = {id: id} options[field] = value + @postUpdate(field, value) @queueUpdate(options) + postUpdate: (field, value) -> + if field == 'onboarding_onboarded_at' + console.log("onboarding onboarded at set") + context.JK.Banner.showAlert({title: "One Last Thing", html: "Please send this student Email #5 right now.

This is a critical email, and if you don't send it now, you'll forget. Thanks!"}); + queueUpdate: (update) -> @updates.push(update) @onUpdate() diff --git a/web/app/assets/javascripts/react-components/BookLesson.js.jsx.coffee b/web/app/assets/javascripts/react-components/BookLesson.js.jsx.coffee index 1e36f0d09..75a5a9989 100644 --- a/web/app/assets/javascripts/react-components/BookLesson.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/BookLesson.js.jsx.coffee @@ -379,6 +379,7 @@ UserStore = context.UserStore : + {window.JK.currentTimezone()} {slot1Errors} @@ -393,7 +394,9 @@ UserStore = context.UserStore : + {window.JK.currentTimezone()} + {slot2Errors} ` diff --git a/web/app/assets/javascripts/react-components/LessonBooking.js.jsx.coffee b/web/app/assets/javascripts/react-components/LessonBooking.js.jsx.coffee index 0171cf344..ed74fafbe 100644 --- a/web/app/assets/javascripts/react-components/LessonBooking.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/LessonBooking.js.jsx.coffee @@ -464,6 +464,10 @@ UserStore = context.UserStore isSuspended: () -> @state.booking?.status == 'suspended' + isUnconfirmed: () -> + @state.booking?.status == 'unconfirmed' + + isStudentCountered: () -> @counteredSlot()?['is_student_created?'] @@ -774,6 +778,11 @@ UserStore = context.UserStore header = 'This lesson has been suspended' content = @renderTeacherSuspended() + else if @isUnconfirmed() + + header = 'This lesson has been automatically canceled' + content = @renderTeacherUnconfirmed() + return `

diff --git a/web/lib/user_manager.rb b/web/lib/user_manager.rb index 02428f26f..01d6cb02e 100644 --- a/web/lib/user_manager.rb +++ b/web/lib/user_manager.rb @@ -46,6 +46,7 @@ class UserManager < BaseManager origin = options[:origin] test_drive_package = options[:test_drive_package] under_13 = options[:under_13] + timezone = options[:timezone] recaptcha_failed = false unless options[:skip_recaptcha] # allow callers to opt-of recaptcha @@ -102,7 +103,8 @@ class UserManager < BaseManager education_interest: education_interest, origin: origin, test_drive_package: test_drive_package, - under_13: under_13) + under_13: under_13, + timezone: timezone) user end diff --git a/web/spec/features/activate_account_spec.rb b/web/spec/features/activate_account_spec.rb index 4b87b3e58..9e6032ac8 100644 --- a/web/spec/features/activate_account_spec.rb +++ b/web/spec/features/activate_account_spec.rb @@ -20,19 +20,20 @@ describe "Activate Account Card", :js => true, :type => :feature, :capybara_feat amazon_2_free_card.credits.should eql 2 - find('h2', text: 'Activate Account') + find('.instructions', text: 'Paste or enter your promotional code so we can validate it and credit you with 2 free lessons.') fill_in "code", with: amazon_2_free_card.code + find('a.amazon-a-button-text', text: 'Submit Code').trigger(:click) + + find('.success-msg wbr', 'Your code has been validated!') fill_in "email", with: "amzposa1@jamkazam.com" fill_in "password", with: "jam123" - find('.redeem-container ins', visible: false).trigger(:click) + find('.checkbox-input input').trigger(:click) - find('button.redeem-giftcard', text: 'ACTIVATE ACCOUNT').trigger(:click) + find('a.amazon-a-button-text', text: 'Create Account').trigger(:click) - find('.jam-class.all-done span.amount-gifted', text: amazon_2_free_card.credits) - find('.done-action a.go-browse').trigger(:click) - - find('h2', text: 'search teachers') + find('.success-msg', "You're all set!") + sleep 3 user = User.find_by_email("amzposa1@jamkazam.com") amazon_2_free_card.reload amazon_2_free_card.user.should eq(user) @@ -40,29 +41,31 @@ describe "Activate Account Card", :js => true, :type => :feature, :capybara_feat amazon_2_free_card.purchased.should be true user.reload user.jamclass_credits.should eq(amazon_2_free_card.credits) + user.timezone.should_not be_nil end it "validates correctly" do visit '/account/activate/code' - find('h2', text: 'Activate Account') + amazon_2_free_card.credits.should eql 2 - find('button.redeem-giftcard', text: 'ACTIVATE ACCOUNT').trigger(:click) - - find('.errors.active', text: "Email can't be blank") - - find('h2', text: 'Activate Account') + find('.instructions', text: 'Paste or enter your promotional code so we can validate it and credit you with 2 free lessons.') fill_in "code", with: amazon_2_free_card.code + find('a.amazon-a-button-text', text: 'Submit Code').trigger(:click) + + find('.success-msg wbr', 'Your code has been validated!') + find('a.amazon-a-button-text', text: 'Create Account').trigger(:click) + find('.error', text: "Email can't be blank") + fill_in "email", with: "amzpos2@jamkazam.com" fill_in "password", with: "jam123" - find('.redeem-container ins', visible: false).trigger(:click) + find('.checkbox-input input').trigger(:click) - find('button.redeem-giftcard', text: 'ACTIVATE ACCOUNT').trigger(:click) + find('a.amazon-a-button-text', text: 'Create Account').trigger(:click) - find('.done-action a.go-browse').trigger(:click) - - find('h2', text: 'search teachers') + find('.success-msg', "You're all set!") + sleep 3 user = User.find_by_email("amzpos2@jamkazam.com") amazon_2_free_card.reload amazon_2_free_card.user.should eq(user) @@ -79,14 +82,15 @@ describe "Activate Account Card", :js => true, :type => :feature, :capybara_feat it "succeeds" do fast_signin(user1, '/account/activate/code') - find('h2', text: 'Activate Account') + find('.instructions', text: 'Paste or enter your promotional code so we can validate it and credit you with 2 free lessons.') + fill_in "code", with: amazon_2_free_card.code - find('button.redeem-giftcard', text: 'ACTIVATE COUPON CODE').trigger(:click) + find('a.amazon-a-button-text', text: 'Submit Code').trigger(:click) - find('.done-action a.go-browse').trigger(:click) + find('a.amazon-a-button-text', text: 'Apply Credits').trigger(:click) - find('h2', text: 'search teachers') + find('.success-msg', text: "You're all set!") user1.reload amazon_2_free_card.reload @@ -101,19 +105,19 @@ describe "Activate Account Card", :js => true, :type => :feature, :capybara_feat it "validates" do fast_signin(user1, '/account/activate/code') - find('h2', text: 'Activate Account') + find('.instructions', text: 'Paste or enter your promotional code so we can validate it and credit you with 2 free lessons.') - find('button.redeem-giftcard').trigger(:click) + find('.amazon-a-button-text', text: 'Submit Code').trigger(:click) - find('.errors.active', text: "This is not a valid code. Please carefully re-enter the code and try again. If it still does not work, please email us at support@jamkazam.com to report this problem.") + find('.error', text: "Code not valid") fill_in "code", with: amazon_2_free_card.code - find('button.redeem-giftcard').trigger(:click) + find('a.amazon-a-button-text', text: 'Submit Code').trigger(:click) - find('.done-action a.go-browse').trigger(:click) + find('a.amazon-a-button-text', text: 'Apply Credits').trigger(:click) - find('h2', text: 'search teachers') + find('.success-msg', text: "You're all set!") user1.reload amazon_2_free_card.reload @@ -121,6 +125,7 @@ describe "Activate Account Card", :js => true, :type => :feature, :capybara_feat amazon_2_free_card.requires_purchase.should be false amazon_2_free_card.purchased.should be true user1.jamclass_credits.should eq(amazon_2_free_card.credits) + user1.timezone.should_not be_nil end end end diff --git a/web/spec/requests/active_music_sessions_api_spec.rb b/web/spec/requests/active_music_sessions_api_spec.rb index 5adb36164..d797a3ed3 100755 --- a/web/spec/requests/active_music_sessions_api_spec.rb +++ b/web/spec/requests/active_music_sessions_api_spec.rb @@ -158,7 +158,7 @@ describe "Active Music Session API ", :type => :api do it "successful" do put "/api/sessions/#{music_session.id}.json", {:description => "you!", :musician_access => false, :fan_chat => false, :fan_access => false, :approval_required => true}.to_json, "CONTENT_TYPE" => 'application/json' - last_response.status.should eql(204) + last_response.status.should eql(200) get "/api/sessions/#{music_session.id}.json", "CONTENT_TYPE" => 'application/json' updated_session = JSON.parse(last_response.body) updated_session["description"].should == "you!" @@ -170,7 +170,7 @@ describe "Active Music Session API ", :type => :api do it "string boolean value" do put "/api/sessions/#{music_session.id}.json", {:musician_access => "false"}.to_json, "CONTENT_TYPE" => 'application/json' - last_response.status.should eql(204) + last_response.status.should eql(200) get "/api/sessions/#{music_session.id}.json", "CONTENT_TYPE" => 'application/json' updated_session = JSON.parse(last_response.body) updated_session["musician_access"].should be false @@ -184,7 +184,7 @@ describe "Active Music Session API ", :type => :api do it "updated genres" do put "/api/sessions/#{music_session.id}.json", {:genre => "jazz"}.to_json, "CONTENT_TYPE" => 'application/json' - last_response.status.should eql(204) + last_response.status.should eql(200) get "/api/sessions/#{music_session.id}.json", "CONTENT_TYPE" => 'application/json' updated_session = JSON.parse(last_response.body) updated_session["genres"].should == ["Jazz"] @@ -523,8 +523,6 @@ describe "Active Music Session API ", :type => :api do join_request = JSON.parse(last_response.body) - puts "join_request #{join_request}" - # now join_requests should still be empty, because we don't share join_requests to people outside the session login(user2) get '/api/sessions.json'