diff --git a/admin/app/admin/jam_ruby_users.rb b/admin/app/admin/jam_ruby_users.rb index cce181faf..c079792e9 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,6 +255,7 @@ 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] diff --git a/admin/app/views/admin/users/_form.html.slim b/admin/app/views/admin/users/_form.html.slim index 2e8d483b9..bb666c081 100644 --- a/admin/app/views/admin/users/_form.html.slim +++ b/admin/app/views/admin/users/_form.html.slim @@ -3,6 +3,7 @@ = 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 :first_name = f.input :last_name = f.input :city diff --git a/db/manifest b/db/manifest index 19a9059d2..3429696ea 100755 --- a/db/manifest +++ b/db/manifest @@ -383,4 +383,5 @@ 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 \ 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/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..58c4baa38 100644 --- a/ruby/lib/jam_ruby/models/lesson_booking.rb +++ b/ruby/lib/jam_ruby/models/lesson_booking.rb @@ -962,6 +962,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 +976,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..922bdb854 100644 --- a/ruby/lib/jam_ruby/models/lesson_session.rb +++ b/ruby/lib/jam_ruby/models/lesson_session.rb @@ -984,6 +984,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 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/user.rb b/ruby/lib/jam_ruby/models/user.rb index baf599bd4..8b7b0083f 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -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" @@ -350,6 +350,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 +1223,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 +1553,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 +1676,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 diff --git a/ruby/spec/jam_ruby/models/lesson_session_spec.rb b/ruby/spec/jam_ruby/models/lesson_session_spec.rb index 9c4b984bc..dba6e68de 100644 --- a/ruby/spec/jam_ruby/models/lesson_session_spec.rb +++ b/ruby/spec/jam_ruby/models/lesson_session_spec.rb @@ -271,19 +271,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 +459,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 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/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/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'