diff --git a/db/manifest b/db/manifest index 04ad4b590..4bc66447b 100755 --- a/db/manifest +++ b/db/manifest @@ -73,3 +73,4 @@ crash_dumps.sql crash_dumps_idx.sql music_sessions_user_history_add_session_removed_at.sql user_progress_tracking.sql +whats_next.sql diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index 14133dc49..ce60126ee 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -124,6 +124,7 @@ module JamRuby validates :terms_of_service, :acceptance => {:accept => true, :on => :create, :allow_nil => false } validates :subscribe_email, :inclusion => {:in => [nil, true, false]} validates :musician, :inclusion => {:in => [true, false]} + validates :show_whats_next, :inclusion => {:in => [nil, true, false]} # custom validators validate :validate_musician_instruments diff --git a/web/app/assets/javascripts/createSession.js b/web/app/assets/javascripts/createSession.js index a8520ec94..303c31145 100644 --- a/web/app/assets/javascripts/createSession.js +++ b/web/app/assets/javascripts/createSession.js @@ -7,7 +7,7 @@ var logger = context.JK.logger; var realtimeMessaging = context.JK.JamServer; var friendSelectorDialog = new context.JK.FriendSelectorDialog(app, friendSelectorCallback); - var invitationDialog = new context.JK.InvitationDialog(app); + var invitationDialog = null; var autoComplete = null; var userNames = []; var userIds = []; @@ -293,7 +293,7 @@ friendSelectorDialog.showDialog(selectedFriendIds); }); - $('.btn-email-invitation').click(function() { + $('div[layout-id="createSession"] .btn-email-invitation').click(function() { invitationDialog.showEmailDialog(); }); @@ -403,9 +403,9 @@ }); } - function initialize() { + function initialize(invitationDialogInstance) { friendSelectorDialog.initialize(); - invitationDialog.initialize(); + invitationDialog = invitationDialogInstance; events(); loadBands(); loadSessionSettings(); diff --git a/web/app/assets/javascripts/invitationDialog.js b/web/app/assets/javascripts/invitationDialog.js index f6598ff72..a3c569f59 100644 --- a/web/app/assets/javascripts/invitationDialog.js +++ b/web/app/assets/javascripts/invitationDialog.js @@ -106,7 +106,6 @@ $('#btn-send-invitation').show(); $('#btn-next-invitation').hide(); clearTextFields(); - app.layout.showDialog('inviteUsers') } diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index 376626928..ea66bcbec 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -293,6 +293,21 @@ }); } + function updateUser(options) { + var id = getId(options); + + delete options['id']; + + return $.ajax({ + type: "POST", + dataType: "json", + contentType: 'application/json', + url: "/api/users/" + id, + data: JSON.stringify(options), + processData: false + }); + } + function initialize() { return self; } @@ -322,6 +337,7 @@ this.userSocialPromoted = userSocialPromoted; this.createJoinRequest = createJoinRequest; this.updateJoinRequest = updateJoinRequest; + this.updateUser = updateUser; return this; }; diff --git a/web/app/assets/javascripts/layout.js b/web/app/assets/javascripts/layout.js index 682f0f7a3..12cfda72a 100644 --- a/web/app/assets/javascripts/layout.js +++ b/web/app/assets/javascripts/layout.js @@ -56,6 +56,8 @@ var dialogBindings = {}; var wizardShowFunctions = {}; + var openDialogs = []; // FIFO stack + function setup() { requiredStyles(); hideAll(); @@ -408,7 +410,8 @@ function closeDialog(dialog) { var $dialog = $('[layout-id="' + dialog + '"]'); dialogEvent(dialog, 'beforeHide'); - $('.dialog-overlay').hide(); + var $overlay = $('.dialog-overlay'); + unstackDialogs($overlay); $dialog.hide(); dialogEvent(dialog, 'afterHide'); } @@ -463,11 +466,42 @@ } } + /** + * Responsible for keeping N dialogs in correct stacked order, + * also moves the .dialog-overlay such that it hides/obscures all dialogs except the highest one + */ + function stackDialogs($dialog, $overlay) { + openDialogs.push($dialog); + var zIndex = 1000; + for(var i in openDialogs) { + var $dialog = openDialogs[i]; + $dialog.css('zIndex', zIndex); + zIndex++; + } + $overlay.css('zIndex', zIndex - 1); + } + + function unstackDialogs($overlay) { + if(openDialogs.length > 0) { + openDialogs.pop(); + } + + var zIndex = 1000 + openDialogs.length; + $overlay.css('zIndex', zIndex - 1); + + if(openDialogs.length == 0) { + $overlay.hide(); + } + } + function showDialog(dialog) { dialogEvent(dialog, 'beforeShow'); - $('.dialog-overlay').show(); + var $overlay = $('.dialog-overlay') + $overlay.show(); centerDialog(dialog); - $('[layout-id="' + dialog + '"]').show(); + var $dialog = $('[layout-id="' + dialog + '"]'); + stackDialogs($dialog, $overlay); + $dialog.show(); dialogEvent(dialog, 'afterShow'); } @@ -547,7 +581,7 @@ this.notify = function(message, descriptor) { var $notify = $('[layout="notify"]'); - + if (notifyQueue.length === 0) { firstNotification = true; setNotificationInfo(message, descriptor); diff --git a/web/app/assets/javascripts/sidebar.js b/web/app/assets/javascripts/sidebar.js index 1ec620e76..1d85a6ca7 100644 --- a/web/app/assets/javascripts/sidebar.js +++ b/web/app/assets/javascripts/sidebar.js @@ -6,8 +6,8 @@ context.JK.Sidebar = function(app) { var logger = context.JK.logger; var friends = []; - var invitationDialog = new context.JK.InvitationDialog(app); var rest = context.JK.Rest(); + var invitationDialog = null; function initializeFriendsPanel() { @@ -423,10 +423,12 @@ // watch for Invite More Users events $('#sidebar-div .btn-email-invitation').click(function() { invitationDialog.showEmailDialog(); + return false; }); $('#sidebar-div .btn-gmail-invitation').click(function() { invitationDialog.showGoogleDialog(); + return false; }); } @@ -661,11 +663,12 @@ }); } - this.initialize = function() { + this.initialize = function(invitationDialogInstance) { events(); initializeFriendsPanel(); initializeChatPanel(); initializeNotificationsPanel(); + invitationDialog = invitationDialogInstance; }; }; })(window,jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/user_dropdown.js b/web/app/assets/javascripts/user_dropdown.js index 462a85269..08c8fac99 100644 --- a/web/app/assets/javascripts/user_dropdown.js +++ b/web/app/assets/javascripts/user_dropdown.js @@ -10,7 +10,7 @@ var logger = context.JK.logger; var rest = new JK.Rest(); var userMe = null; - var invitationDialog = new context.JK.InvitationDialog(app); + var invitationDialog = null; function menuHoverIn() { $('ul.shortcuts', this).show(); @@ -52,6 +52,7 @@ // TODO - Setting global variable for local user. context.JK.userMe = r; updateHeader(); + handleWhatsNext(userMe); }).fail(app.ajaxError); } @@ -60,6 +61,13 @@ showAvatar(); } + function handleWhatsNext(userProfile) { + if(gon.isNativeClient && userProfile.show_whats_next) { + app.layout.showDialog('whatsNext'); + } + } + + // initially show avatar function showAvatar() { var photoUrl = context.JK.resolveAvatarUrl(userMe.photo_url); @@ -76,9 +84,9 @@ $('#header-avatar').replaceWith(avatar); } - this.initialize = function() { + this.initialize = function(invitationDialogInstance) { events(); - invitationDialog.initialize(); + invitationDialog = invitationDialogInstance; loadMe(); } } diff --git a/web/app/assets/javascripts/whatsNextDialog.js b/web/app/assets/javascripts/whatsNextDialog.js new file mode 100644 index 000000000..33c502b63 --- /dev/null +++ b/web/app/assets/javascripts/whatsNextDialog.js @@ -0,0 +1,55 @@ +(function(context,$) { + + "use strict"; + context.JK = context.JK || {}; + context.JK.WhatsNextDialog = function(app) { + var logger = context.JK.logger; + var rest = context.JK.Rest(); + var invitationDialog = null; + + + function registerEvents() { + + $('#whatsnext-dialog a.facebook-invite').on('click', function(e) { + alert("This feature not yet implemented"); + }); + + $('#whatsnext-dialog a.google-invite').on('click', function(e) { + invitationDialog.showGoogleDialog(); + }); + + $('#whatsnext-dialog a.email-invite').on('click', function(e) { + invitationDialog.showEmailDialog(); + }); + } + function beforeShow() { + } + + function beforeHide() { + var $dontShowWhatsNext = $('#show_whats_next'); + + if($dontShowWhatsNext.is(':checked')) { + rest.updateUser( {show_whats_next:false}) + } + + } + + function initialize(invitationDialogInstance) { + var dialogBindings = { + 'beforeShow' : beforeShow, + 'beforeHide': beforeHide + }; + + app.bindDialog('whatsNext', dialogBindings); + + registerEvents(); + + invitationDialog = invitationDialogInstance; + }; + + + this.initialize = initialize; + } + + return this; +})(window,jQuery); \ No newline at end of file diff --git a/web/app/assets/stylesheets/client/ftue.css.scss b/web/app/assets/stylesheets/client/ftue.css.scss index a1cb9228d..9af975c1a 100644 --- a/web/app/assets/stylesheets/client/ftue.css.scss +++ b/web/app/assets/stylesheets/client/ftue.css.scss @@ -299,6 +299,50 @@ div[layout-id="ftue3"] { border: 1px solid #ed3618; } +.ftue-overlay.tall { + top:75px; +} +.ftue-overlay.tall .ftue-inner { + padding:5px; +} + + +#whatsnext-dialog { + + height:auto; + + .ftue-inner h2 { + font-weight:normal; + color:#ed3618; + margin-bottom:6px; + font-size:1.7em; + } + + .ftue-inner table td.whatsnext { + font-size:12px; + padding:10px; + width:50%; + font-weight:300; + } + + .ftue-inner table td.whatsnext { + font-size:12px; + padding:10px; + width:50%; + font-weight:300; + } + + .ftue-inner table a { + text-decoration:none; + } + + .ftue-inner table { + border-collapse:separate; + border-spacing: 20px; + } +} + + .ftue-inner { width:750px; padding:25px; diff --git a/web/app/assets/stylesheets/client/jamkazam.css.scss b/web/app/assets/stylesheets/client/jamkazam.css.scss index 2a2df361e..407f0230c 100644 --- a/web/app/assets/stylesheets/client/jamkazam.css.scss +++ b/web/app/assets/stylesheets/client/jamkazam.css.scss @@ -131,6 +131,10 @@ input[type="button"] { font-size:11px; } +.orange { + color: $ColorScreenPrimary !important; +} + .curtain { background-color: $ColorScreenBackground; position:absolute; @@ -433,6 +437,9 @@ input[type="text"], input[type="password"]{ width:100%; } +.f20 { + font-size: 20px !important; +} /* TODO - we need a separate stylesheet(s) for signin/signup screens */ /* Following is a style adjustment for the sign-in table spacing */ #sign-in td { padding: 4px; } diff --git a/web/app/controllers/api_users_controller.rb b/web/app/controllers/api_users_controller.rb index e60c896ba..f463da6d3 100644 --- a/web/app/controllers/api_users_controller.rb +++ b/web/app/controllers/api_users_controller.rb @@ -65,6 +65,7 @@ class ApiUsersController < ApiController @user.country = params[:country] if params.has_key?(:country) @user.musician = params[:musician] if params.has_key?(:musician) @user.update_instruments(params[:instruments].nil? ? [] : params[:instruments]) if params.has_key?(:instruments) + @user.show_whats_next = params[:show_whats_next] if params.has_key?(:show_whats_next) @user.save diff --git a/web/app/controllers/clients_controller.rb b/web/app/controllers/clients_controller.rb index 043c1efd7..f7a96de70 100644 --- a/web/app/controllers/clients_controller.rb +++ b/web/app/controllers/clients_controller.rb @@ -21,6 +21,9 @@ class ClientsController < ApplicationController end end + # let javascript have access to the server's opinion if this is a native client + gon.isNativeClient = @nativeClient + if current_user render :layout => 'client' else diff --git a/web/app/views/api_users/show.rabl b/web/app/views/api_users/show.rabl index 09a5bb4bf..e54a66772 100644 --- a/web/app/views/api_users/show.rabl +++ b/web/app/views/api_users/show.rabl @@ -1,6 +1,6 @@ object @user -attributes :id, :first_name, :last_name, :name, :city, :state, :country, :location, :online, :photo_url, :musician, :gender, :birth_date, :internet_service_provider, :friend_count, :liker_count, :like_count, :band_like_count, :follower_count, :following_count, :band_following_count, :recording_count, :session_count +attributes :id, :first_name, :last_name, :name, :city, :state, :country, :location, :online, :photo_url, :musician, :gender, :birth_date, :internet_service_provider, :friend_count, :liker_count, :like_count, :band_like_count, :follower_count, :following_count, :band_following_count, :recording_count, :session_count, :show_whats_next # give back more info if the user being fetched is yourself if @user == current_user diff --git a/web/app/views/clients/_createSession.html.erb b/web/app/views/clients/_createSession.html.erb index 117b79870..00539f0d8 100644 --- a/web/app/views/clients/_createSession.html.erb +++ b/web/app/views/clients/_createSession.html.erb @@ -108,7 +108,7 @@
-
+ -->
-
+
<%= image_tag("content/icon_google.png", :size => "24x24", :align => "absmiddle") %> diff --git a/web/app/views/clients/_sidebar.html.erb b/web/app/views/clients/_sidebar.html.erb index b56293000..4ac3fb7f7 100644 --- a/web/app/views/clients/_sidebar.html.erb +++ b/web/app/views/clients/_sidebar.html.erb @@ -60,7 +60,7 @@
-
+
-
+
<%= image_tag("content/icon_google.png", :size => "24x24", :align => "absmiddle") %> diff --git a/web/app/views/clients/_whatsNextDialog.html b/web/app/views/clients/_whatsNextDialog.html new file mode 100644 index 000000000..e1402ffe2 --- /dev/null +++ b/web/app/views/clients/_whatsNextDialog.html @@ -0,0 +1,70 @@ + +
+ +
+

what's next?

+
+ +
+ +
+ + + + + + + + + + + +
+

INVITE YOUR FRIENDS

+ JamKazam is a very new service, so we don't have a bunch of users yet. This will make it + harder to jump into existing sessions with others for a while. So invite other musicians you + know to join using the buttons below, and then schedule a time to meet up (online) and play!

+      +      + <%= image_tag "content/icon_google.png", {:align=>"absmiddle", :height => 26, :width => 26 } %> Google+ +
+

FIND MUSICIANS

+ +
+ <%= image_tag "content/ftue_whatsnext_musicians.png", {:height => 115, :width => 107 } %> +
+ Click the Musicians tile on the home screen to find other musicians in your area. Use the + Message button to say hello, or the Connect button to request a friend connection. Then get + a session set up with each other. +
+

FIND OR CREATE A SESSION

+ Click the Find Session tile on the home screen to see if there are any active public + sessions in your area that you can join. If there aren't, use the Create Session tile to + make your own session.

+ <%= image_tag "content/ftue_whatsnext_create.png", {:height => 67, :width => 313 } %> +
+

WATCH VIDEOS

+ Watch tuturial videos on YouTube to learn how to use the key features of the JamKazam + service. +

+ + + +
+ + + Don't show this again +     + CLOSE
+
+
+
+ +
+
diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb index 47f35ce9b..309a59786 100644 --- a/web/app/views/clients/index.html.erb +++ b/web/app/views/clients/index.html.erb @@ -30,6 +30,7 @@ <%= render "account_profile_avatar" %> <%= render "account_audio_profile" %> <%= render "invitationDialog" %> +<%= render "whatsNextDialog" %> <%= render "notify" %> <%= render "client_update" %> <%= render "banner" %> @@ -86,16 +87,19 @@ // Some things can't be initialized until we're connected. Put them here. function _initAfterConnect() { + var invitationDialog = new JK.InvitationDialog(JK.app); + invitationDialog.initialize(); + var userDropdown = new JK.UserDropdown(JK.app); JK.UserDropdown = userDropdown; - userDropdown.initialize(); + userDropdown.initialize(invitationDialog); var header = new JK.Header(JK.app); JK.Header = header; header.initialize(); var sidebar = new JK.Sidebar(JK.app); - sidebar.initialize(); + sidebar.initialize(invitationDialog); var homeScreen = new JK.HomeScreen(JK.app); homeScreen.initialize(); @@ -126,7 +130,7 @@ JK.Banner.initialize(); var createSessionScreen = new JK.CreateSessionScreen(JK.app); - createSessionScreen.initialize(); + createSessionScreen.initialize(invitationDialog); var findSessionScreen = new JK.FindSessionScreen(JK.app); var sessionLatency = null; @@ -140,6 +144,10 @@ var sessionSettingsDialog = new JK.SessionSettingsDialog(JK.app, sessionScreen); sessionSettingsDialog.initialize(); + + var whatsNextDialog = new JK.WhatsNextDialog(JK.app); + whatsNextDialog.initialize(invitationDialog); + var ftueWizard = new JK.FtueWizard(JK.app); ftueWizard.initialize(); diff --git a/web/app/views/layouts/corporate.html.erb b/web/app/views/layouts/corporate.html.erb index 683972a5b..410438e7f 100644 --- a/web/app/views/layouts/corporate.html.erb +++ b/web/app/views/layouts/corporate.html.erb @@ -9,6 +9,7 @@ <% end %> + <%= include_gon(:init => true) %> <%= javascript_include_tag "corp/corporate" %> <%= csrf_meta_tags %> diff --git a/web/app/views/layouts/landing.erb b/web/app/views/layouts/landing.erb index f3328c7b7..4a30aba65 100644 --- a/web/app/views/layouts/landing.erb +++ b/web/app/views/layouts/landing.erb @@ -12,7 +12,7 @@ <% end %> - <%= include_gon %> + <%= include_gon(:init => true) %> <%= csrf_meta_tags %> diff --git a/web/app/views/layouts/web.erb b/web/app/views/layouts/web.erb index 367661615..54504c649 100644 --- a/web/app/views/layouts/web.erb +++ b/web/app/views/layouts/web.erb @@ -12,7 +12,7 @@ <% end %> - <%= include_gon %> + <%= include_gon(:init => true) %> <%= csrf_meta_tags %> @@ -62,8 +62,11 @@ JK.app = JK.JamKazam(); JK.app.initialize({inClient: false, layoutOpts: {layoutFooter:false}}); + var invitationDialog = new JK.InvitationDialog(JK.app); + invitationDialog.initialize(); + var userDropdown = new JK.UserDropdown(JK.app); - userDropdown.initialize(); + userDropdown.initialize(invitationDialog); } }) diff --git a/web/spec/factories.rb b/web/spec/factories.rb index b38fbb1cd..9285cfd89 100644 --- a/web/spec/factories.rb +++ b/web/spec/factories.rb @@ -8,6 +8,7 @@ FactoryGirl.define do password "foobar" password_confirmation "foobar" email_confirmed true + show_whats_next = false #annoying for testing, usually musician true city "Apex" state "NC" diff --git a/web/spec/features/whats_next_spec.rb b/web/spec/features/whats_next_spec.rb new file mode 100644 index 000000000..f4a96eca8 --- /dev/null +++ b/web/spec/features/whats_next_spec.rb @@ -0,0 +1,74 @@ +require 'spec_helper' + +describe "Home Screen", :js => true, :type => :feature, :capybara_feature => true do + + subject { page } + + before(:all) do + Capybara.javascript_driver = :poltergeist + Capybara.current_driver = Capybara.javascript_driver + Capybara.default_wait_time = 10 + end + + before(:each) do + sign_in_poltergeist user + page.driver.headers = { 'User-Agent' => ' JamKazam ' } + visit "/" + + end + + let(:user) { FactoryGirl.create(:user, :show_whats_next => true) } + + describe "new user in the native view should see the whats next dialog" do + + it { + should have_selector('h1', text: 'what\'s next?') + } + + describe "open invitation dialog for email" do + before(:each) do + find('#whatsnext-dialog .email-invite').trigger(:click) + end + + it {should have_selector('label', text: 'Enter email address(es). If multiple addresses, separate with commas.')} + end + + + describe "launches youtube tutorial site" do + + it { + find("#whatsnext-dialog a.orange[purpose='youtube-tutorials']").trigger(:click) + page.driver.window_handles.last + page.within_window page.driver.window_handles.last do + should have_title('JamKazam - YouTube') + end + } + end + + + describe "close hides the screen" do + + it { + find('#whatsnext-dialog a[layout-action="close"]').trigger(:click) + should have_no_selector('h1', text: 'what\'s next?') + } + end + + describe "user can make prompt go away forever" do + + it { + find('#show_whats_next').trigger(:click) + find('#whatsnext-dialog a[layout-action="close"]').trigger(:click) + + # needed because we poke the server with an updateUser call, but their is no indication in the UI that it's done + wait_for_ajax + page.driver.headers = { 'User-Agent' => ' JamKazam ' } + visit "/" + wait_until_curtain_gone + should_not have_selector('h1', text: 'what\'s next?') + } + end + + end +end + diff --git a/web/spec/spec_helper.rb b/web/spec/spec_helper.rb index 3596b2490..512f00f07 100644 --- a/web/spec/spec_helper.rb +++ b/web/spec/spec_helper.rb @@ -75,6 +75,7 @@ Spork.prefork do # config.mock_with :flexmock # config.mock_with :rr config.mock_with :rspec + config.color_enabled = true # by default, do not run tests marked as 'slow' config.filter_run_excluding slow: true unless ENV['RUN_SLOW_TESTS'] == "1" diff --git a/web/spec/support/utilities.rb b/web/spec/support/utilities.rb index e91b1bb03..aa42d19af 100644 --- a/web/spec/support/utilities.rb +++ b/web/spec/support/utilities.rb @@ -34,3 +34,32 @@ def sign_in_poltergeist(user) page.driver.browser.manage.add_cookie :name => :remember_token, :value => user.remember_token end end + + +def wait_for_ajax(wait=Capybara.default_wait_time) + wait = wait * 10 #(because we sleep .1) + + counter = 0 + while page.execute_script("$.active").to_i > 0 + counter += 1 + sleep(0.1) + raise "AJAX request took longer than #{wait} seconds." if counter >= wait + end +end + +# waits until the user object has been requested, which comes after the 'curtain' is lifted +# and after a call to /api/user/:id for the current user is called initially +def wait_until_user(wait=Capybara.default_wait_time) + wait = wait * 10 #(because we sleep .1) + + counter = 0 + # while page.execute_script("$('.curtain').is(:visible)") == "true" + # counter += 1 + # sleep(0.1) + # raise "Waiting for user to populate took longer than #{wait} seconds." if counter >= wait + # end +end + +def wait_until_curtain_gone + should have_no_selector('.curtain') +end \ No newline at end of file