* VRFS-780 - finished, tested, etc

This commit is contained in:
Seth Call 2013-10-21 17:13:53 -05:00
parent 4b455b6d77
commit 32a4297569
25 changed files with 384 additions and 25 deletions

View File

@ -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

View File

@ -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

View File

@ -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();

View File

@ -106,7 +106,6 @@
$('#btn-send-invitation').show();
$('#btn-next-invitation').hide();
clearTextFields();
app.layout.showDialog('inviteUsers')
}

View File

@ -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;
};

View File

@ -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);

View File

@ -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);

View File

@ -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();
}
}

View File

@ -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);

View File

@ -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;

View File

@ -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; }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -108,7 +108,7 @@
</div>
<div style="width:78%">
<div class="left mr20">
<div class="left" layout-link="inviteUsers">
<div class="left">
<a class="btn-email-invitation">
<%= image_tag("content/icon_gmail.png", :size => "24x24", :align => "absmiddle") %>
</a>
@ -134,7 +134,7 @@
<div class="right mt5 ml5">Twitter</div>
</div> -->
<div class="left left">
<div class="left" layout-link="inviteUsers">
<div class="left">
<a class="btn-gmail-invitation">
<%= image_tag("content/icon_google.png", :size => "24x24", :align => "absmiddle") %>
</a>

View File

@ -60,7 +60,7 @@
<br clear="all"/>
<div class="invitation-button-holder">
<div class="left mr20">
<div class="left" layout-link="inviteUsers">
<div class="left">
<a class="btn-email-invitation">
<%= image_tag("content/icon_gmail.png", :size => "24x24", :align => "absmiddle") %>
</a>
@ -68,7 +68,7 @@
<div class="right mt5 ml5">E-mail</div>
</div>
<div class="left left">
<div class="left" layout-link="inviteUsers">
<div class="left">
<a class="btn-gmail-invitation">
<%= image_tag("content/icon_google.png", :size => "24x24", :align => "absmiddle") %>
</a>

View File

@ -0,0 +1,70 @@
<!-- Invitation Dialog -->
<div class="dialog whatsnext-overlay ftue-overlay tall" layout="dialog" layout-id="whatsNext" id="whatsnext-dialog">
<div class="content-head">
<h1>what's next?</h1>
</div>
<div class="ftue-inner">
<div align="center">
<table width="790" cellspacing="20">
<tbody>
<tr>
<td valign="top" bgcolor="#000" class="whatsnext">
<h2>INVITE YOUR FRIENDS</h2>
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!<br><br>
<a href="#" class="facebook-invite"><%= image_tag "content/icon_facebook.png", {:align=>"absmiddle", :height => 24, :width => 24} %>&nbsp;Facebook</a>&nbsp;&nbsp;&nbsp;&nbsp;
<a href="#" class="email-invite"><%= image_tag "content/icon_gmail.png", {:align=>"absmiddle", :height => 24, :width => 24} %>&nbsp;E-mail</a>&nbsp;&nbsp;&nbsp;&nbsp;
<a href="#" class="google-invite"><%= image_tag "content/icon_google.png", {:align=>"absmiddle", :height => 26, :width => 26 } %>&nbsp;Google+</a>
</td>
<td valign="top" bgcolor="#000" class="whatsnext">
<h2>FIND MUSICIANS</h2>
<div class="left mr10">
<%= image_tag "content/ftue_whatsnext_musicians.png", {:height => 115, :width => 107 } %>
</div>
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.
</td>
</tr>
<tr>
<td valign="top" bgcolor="#000" class="whatsnext">
<h2>FIND OR CREATE A SESSION</h2>
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.<br><br>
<%= image_tag "content/ftue_whatsnext_create.png", {:height => 67, :width => 313 } %>
</td>
<td valign="top" bgcolor="#000" class="whatsnext">
<h2>WATCH VIDEOS</h2>
Watch tuturial videos on YouTube to learn how to use the key features of the JamKazam
service.
<br><br>
<div class="left f20"><br>
<a purpose="youtube-tutorials" rel="external" href="http://www.youtube.com/channel/UC38nc9MMZgExJAd7ca3rkUA" class="orange">See a List of<br>
Videos »</a></div>
<div class="right mr20">
<a purpose="youtube-tutorials" rel="external" href="http://www.youtube.com/channel/UC38nc9MMZgExJAd7ca3rkUA">
<%= image_tag "content/ftue_whatsnext_videos.png", {:height => 90, :width => 152 } %>
</a>
</div>
</td>
</tr>
</tbody>
</table>
<input type="checkbox" id="show_whats_next"> Don't show this again
&nbsp;&nbsp;&nbsp;
<a href="#" class="button-orange" layout-action="close">CLOSE</a><br>
<br>
</div>
</div>
</div>
</div>

View File

@ -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();

View File

@ -9,6 +9,7 @@
<!-- THIS NEEDS TO BE IN FRONT OF ANY OTHER JAVASCRIPT INCLUDES ACCORDING TO BUGSNAG -->
<script src="//d2wy8f7a9ursnm.cloudfront.net/bugsnag-1.0.9.min.js" data-apikey="<%= Rails.application.config.bugsnag_key %>"></script>
<% end %>
<%= include_gon(:init => true) %>
<%= javascript_include_tag "corp/corporate" %>
<%= csrf_meta_tags %>
</head>

View File

@ -12,7 +12,7 @@
<!-- THIS NEEDS TO BE IN FRONT OF ANY OTHER JAVASCRIPT INCLUDES ACCORDING TO BUGSNAG -->
<script src="//d2wy8f7a9ursnm.cloudfront.net/bugsnag-1.0.9.min.js" data-apikey="<%= Rails.application.config.bugsnag_key %>"></script>
<% end %>
<%= include_gon %>
<%= include_gon(:init => true) %>
<%= csrf_meta_tags %>
</head>
<body>

View File

@ -12,7 +12,7 @@
<!-- THIS NEEDS TO BE IN FRONT OF ANY OTHER JAVASCRIPT INCLUDES ACCORDING TO BUGSNAG -->
<script src="//d2wy8f7a9ursnm.cloudfront.net/bugsnag-1.0.9.min.js" data-apikey="<%= Rails.application.config.bugsnag_key %>"></script>
<% end %>
<%= include_gon %>
<%= include_gon(:init => true) %>
<%= csrf_meta_tags %>
</head>
<body class="web">
@ -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);
}
})
</script>

View File

@ -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"

View File

@ -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

View File

@ -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"

View File

@ -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