diff --git a/ruby/.gitignore b/ruby/.gitignore
index a35fe92d7..be1654c09 100644
--- a/ruby/.gitignore
+++ b/ruby/.gitignore
@@ -15,7 +15,7 @@ spec/reports
test/tmp
test/version_tmp
tmp
-
+vendor
.idea
*~
*.swp
diff --git a/ruby/lib/jam_ruby/constants/limits.rb b/ruby/lib/jam_ruby/constants/limits.rb
index 5d5e409d0..52e2036f1 100644
--- a/ruby/lib/jam_ruby/constants/limits.rb
+++ b/ruby/lib/jam_ruby/constants/limits.rb
@@ -2,7 +2,7 @@ module Limits
# band genres
MIN_GENRES_PER_BAND = 1
- MAX_GENRES_PER_BAND = 1
+ MAX_GENRES_PER_BAND = 3
# recording genres
MIN_GENRES_PER_RECORDING = 1
diff --git a/ruby/lib/jam_ruby/models/band.rb b/ruby/lib/jam_ruby/models/band.rb
index d853050b7..50f18486c 100644
--- a/ruby/lib/jam_ruby/models/band.rb
+++ b/ruby/lib/jam_ruby/models/band.rb
@@ -131,34 +131,22 @@ module JamRuby
end
# name
- unless name.nil?
- band.name = name
- end
+ band.name = name unless name.nil?
# website
- unless website.nil?
- band.website = website
- end
+ band.website = website unless website.nil?
# biography
- unless biography.nil?
- band.biography = biography
- end
+ band.biography = biography unless biography.nil?
# city
- unless city.nil?
- band.city = city
- end
+ band.city = city unless city.nil?
# state
- unless state.nil?
- band.state = state
- end
+ band.state = state unless state.nil?
# country
- unless country.nil?
- band.country = country
- end
+ band.country = country unless country.nil?
# genres
unless genres.nil?
@@ -177,14 +165,10 @@ module JamRuby
end
# photo url
- unless photo_url.nil?
- band.photo_url = photo_url
- end
+ band.photo_url = photo_url unless photo_url.nil?
# logo url
- unless logo_url.nil?
- band.logo_url = logo_url
- end
+ band.logo_url = logo_url unless logo_url.nil?
band.updated_at = Time.now.getutc
band.save
diff --git a/web/app/assets/images/shared/avatar_generic_band.png b/web/app/assets/images/shared/avatar_generic_band.png
new file mode 100644
index 000000000..4a883b004
Binary files /dev/null and b/web/app/assets/images/shared/avatar_generic_band.png differ
diff --git a/web/app/assets/javascripts/band_setup.js b/web/app/assets/javascripts/band_setup.js
new file mode 100644
index 000000000..83358a970
--- /dev/null
+++ b/web/app/assets/javascripts/band_setup.js
@@ -0,0 +1,266 @@
+(function(context,$) {
+
+ "use strict";
+
+ context.JK = context.JK || {};
+
+ // TODO: MUCH OF THIS CLASS IS REPEATED IN THE FOLLOWING FILES:
+ // createSession.js
+ // accounts_profiles.js
+
+ context.JK.BandSetupScreen = function(app) {
+ var logger = context.JK.logger;
+ var rest = context.JK.Rest();
+ var friendSelectorDialog = null;
+ var autoComplete = null;
+ var userNames = [];
+ var userIds = [];
+ var userPhotoUrls = [];
+ var selectedFriendIds = {};
+
+ function resetForm() {
+
+ }
+
+ // function formatGenres(genres) {
+ // var formattedGenres = '';
+ // if (genres) {
+ // for (var i=0; i < genres.length; i++) {
+ // var genre = genres[i];
+ // formattedGenres += genre.description;
+ // if (i < genres.length -1) {
+ // formattedGenres += ', ';
+ // }
+ // }
+ // }
+ // return formattedGenres;
+ // }
+
+ function getSelectedGenres() {
+ var genres = [];
+ $('input[type=checkbox]:checked', '#band-genres').each(function(i) {
+ var genre = $(this).val();
+ genres.push(genre);
+ });
+ logger.debug("genres.length=" + genres.length);
+ return genres;
+ }
+
+ function validateForm() {
+ var isValid = true;
+ // name
+
+ // country
+
+ // state
+
+ // city
+
+ // genres (no more than 3)
+
+ // description
+
+
+ return isValid;
+ }
+
+ function createBand() {
+ if (validateForm()) {
+ var band = {};
+ band.name = $("#band-name").val();
+ band.website = $("#band-website").val();
+ band.biography = $("#band-biography").val();
+ band.city = $("#band-city").val();
+ band.state = $("#band-state").val();
+ band.country = $("#band-country").val();
+ band.genres = getSelectedGenres();
+
+ rest.createBand(band).done(function(response) {
+ createBandInvitations(response.id, function() {
+ context.location = "#/bandProfile/" + response.id;
+ });
+ });
+ }
+ }
+
+ function createBandInvitations(bandId, onComplete) {
+ var callCount = 0;
+ var totalInvitations = 0;
+ $('#selected-band-invitees .invitation').each(function(index, invitation) {
+ callCount++;
+ totalInvitations++;
+ var userId = $(invitation).attr('user-id');
+ rest.createBandInvitation(bandId, userId)
+ .done(function(response) {
+ callCount--;
+ }).fail(app.ajaxError);
+ });
+
+ function checker() {
+ if (callCount === 0) {
+ onComplete();
+ } else {
+ context.setTimeout(checker, 10);
+ }
+ }
+ checker();
+ return totalInvitations;
+ }
+
+ function beforeShow(data) {
+ userNames = [];
+ userIds = [];
+ userPhotoUrls = [];
+ resetForm();
+ }
+
+ function afterShow(data) {
+ loadFriends();
+ loadGenres();
+ loadCountries();
+ }
+
+ function loadFriends() {
+ $.ajax({
+ type: "GET",
+ url: "/api/users/" + context.JK.currentUserId + "/friends",
+ async: false
+ }).done(function(response) {
+ $.each(response, function() {
+ userNames.push(this.name);
+ userIds.push(this.id);
+ userPhotoUrls.push(this.photo_url);
+ });
+
+ var autoCompleteOptions = {
+ lookup: { suggestions: userNames, data: userIds },
+ onSelect: addInvitation
+ };
+ if (!autoComplete) {
+ autoComplete = $('#band-invitee-input').autocomplete(autoCompleteOptions);
+ }
+ else {
+ autoComplete.setOptions(autoCompleteOptions);
+ }
+ });
+
+ $(".autocomplete").width("150px");
+ }
+
+ function loadGenres() {
+ rest.getGenres().done(function(response) {
+ $.each(response, function(index, val) {
+ var genreTemplate = $('#template-band-setup-genres').html();
+ var genreHtml = context.JK.fillTemplate(genreTemplate, {
+ id: val.id,
+ description: val.description
+ });
+
+ $('#band-genres').append(genreHtml);
+ });
+ });
+ }
+
+ function loadCountries() {
+
+ }
+
+ function loadStates(country) {
+
+ }
+
+ function loadCities(state) {
+
+ }
+
+ function friendSelectorCallback(newSelections) {
+ var keys = Object.keys(newSelections);
+ for (var i=0; i < keys.length; i++) {
+ addInvitation(newSelections[keys[i]].userName, newSelections[keys[i]].userId);
+ }
+ }
+
+ function addInvitation(value, data) {
+ if ($('#selected-band-invitees div[user-id=' + data + ']').length === 0) {
+ var template = $('#template-band-invitation').html();
+ var invitationHtml = context.JK.fillTemplate(template, {userId: data, userName: value});
+ $('#selected-band-invitees').append(invitationHtml);
+ $('#band-invitee-input').select();
+ selectedFriendIds[data] = true;
+ }
+ else {
+ $('#band-invitee-input').select();
+ context.alert('Invitation already exists for this musician.');
+ }
+ }
+
+ function removeInvitation(evt) {
+ delete selectedFriendIds[$(evt.currentTarget).parent().attr('user-id')];
+ $(evt.currentTarget).closest('.invitation').remove();
+ }
+
+ function events() {
+ $('#selected-band-invitees').on("click", ".invitation a", removeInvitation);
+
+ // friend input focus
+ $('#band-invitee-input').focus(function() {
+ $(this).val('');
+ });
+
+ // friend input blur
+ $('#band-invitee-input').blur(function() {
+ $(this).val('Type a friend\'s name');
+ });
+
+ $('#btn-band-setup-cancel').click(function() {
+ context.location = "#/";
+ });
+
+ $('#btn-band-setup-next').click(function() {
+ $("#band-setup-step-2").show();
+ $("#band-setup-step-1").hide();
+ });
+
+ $('#btn-band-setup-back').click(function() {
+ $("#band-setup-step-1").show();
+ $("#band-setup-step-2").hide();
+ });
+
+ $('#btn-band-setup-create').click(createBand);
+
+ $('#btn-band-choose-friends').click(function() {
+ friendSelectorDialog.showDialog(selectedFriendIds);
+ });
+
+ $('#band-country').on('change', function(evt) {
+ evt.stopPropagation();
+ loadStates(this.value);
+ return false;
+ });
+
+ $('#band-state').on('change', function(evt) {
+ evt.stopPropagation();
+ loadCities(this.value);
+ return false;
+ });
+ }
+
+ function initialize(friendSelectorDialogInstance) {
+ friendSelectorDialog = friendSelectorDialogInstance;
+ friendSelectorDialog.setCallback(friendSelectorCallback);
+ events();
+
+ var screenBindings = {
+ 'beforeShow': beforeShow,
+ 'afterShow': afterShow
+ };
+
+ app.bindScreen('band/setup', screenBindings);
+ }
+
+ this.initialize = initialize;
+ this.afterShow = afterShow;
+ return this;
+ };
+
+ })(window,jQuery);
\ No newline at end of file
diff --git a/web/app/assets/javascripts/createSession.js b/web/app/assets/javascripts/createSession.js
index 9f697366f..a5fd5101e 100644
--- a/web/app/assets/javascripts/createSession.js
+++ b/web/app/assets/javascripts/createSession.js
@@ -6,7 +6,7 @@
context.JK.CreateSessionScreen = function(app) {
var logger = context.JK.logger;
var realtimeMessaging = context.JK.JamServer;
- var friendSelectorDialog = new context.JK.FriendSelectorDialog(app, friendSelectorCallback);
+ var friendSelectorDialog = null;
var invitationDialog = null;
var autoComplete = null;
var userNames = [];
@@ -36,16 +36,16 @@
userPhotoUrls.push(this.photo_url);
});
- var autoCompleteOptions = {
- lookup: { suggestions: userNames, data: userIds },
- onSelect: addInvitation
- };
- if (!autoComplete) {
- autoComplete = $('#friend-input').autocomplete(autoCompleteOptions);
- }
- else {
- autoComplete.setOptions(autoCompleteOptions);
- }
+ // var autoCompleteOptions = {
+ // lookup: { suggestions: userNames, data: userIds },
+ // onSelect: addInvitation
+ // };
+ // if (!autoComplete) {
+ // autoComplete = $('#friend-input').autocomplete(autoCompleteOptions);
+ // }
+ // else {
+ // autoComplete.setOptions(autoCompleteOptions);
+ // }
});
// var autoCompleteOptions = {
@@ -419,8 +419,9 @@
});
}
- function initialize(invitationDialogInstance) {
- friendSelectorDialog.initialize();
+ function initialize(invitationDialogInstance, friendSelectorDialogInstance) {
+ friendSelectorDialog = friendSelectorDialogInstance;
+ friendSelectorDialog.setCallback(friendSelectorCallback);
invitationDialog = invitationDialogInstance;
events();
loadBands();
diff --git a/web/app/assets/javascripts/friendSelector.js b/web/app/assets/javascripts/friendSelector.js
index 4e76ea128..ae5bbeba5 100644
--- a/web/app/assets/javascripts/friendSelector.js
+++ b/web/app/assets/javascripts/friendSelector.js
@@ -3,11 +3,12 @@
"use strict";
context.JK = context.JK || {};
- context.JK.FriendSelectorDialog = function(app, saveCallback) {
+ context.JK.FriendSelectorDialog = function(app) {
var logger = context.JK.logger;
var rest = context.JK.Rest();
var selectedIds = {};
var newSelections = {};
+ var mySaveCallback;
function events() {
$('#btn-save-friends').click(saveFriendInvitations);
@@ -63,7 +64,7 @@
function saveFriendInvitations(evt) {
evt.stopPropagation();
- saveCallback(newSelections);
+ mySaveCallback(newSelections);
}
function showDialog(ids) {
@@ -78,6 +79,10 @@
events();
};
+ this.setCallback = function(callback) {
+ mySaveCallback = callback;
+ }
+
this.showDialog = showDialog;
return this;
};
diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js
index d0dafc98d..1768dfd4d 100644
--- a/web/app/assets/javascripts/jam_rest.js
+++ b/web/app/assets/javascripts/jam_rest.js
@@ -44,6 +44,58 @@
});
}
+ function createBand(band) {
+ return $.ajax({
+ type: "POST",
+ dataType: "json",
+ url: '/api/bands',
+ contentType: 'application/json',
+ processData: false,
+ data: JSON.stringify(band)
+ });
+ }
+
+ function updateBand(band) {
+ return $.ajax({
+ type: "POST",
+ dataType: "json",
+ url: '/api/bands/' + band.id,
+ contentType: 'application/json',
+ processData: false,
+ data: JSON.stringify(band)
+ });
+ }
+
+ function createBandInvitation(bandId, userId) {
+ var bandInvitation = {
+ band_id: bandId,
+ user_id: userId
+ };
+
+ return $.ajax({
+ type: "POST",
+ dataType: "json",
+ url: '/api/bands/' + bandId + "/invitations",
+ contentType: 'application/json',
+ processData: false,
+ data: JSON.stringify(bandInvitation)
+ });
+ }
+
+ function updateBandInvitation(bandId) {
+ var bandInvitation = {};
+ bandInvitation.band_id = bandId;
+ bandInvitation.user_id = userId;
+ return $.ajax({
+ type: "POST",
+ dataType: "json",
+ url: '/api/bands/' + bandId + "/invitations",
+ contentType: 'application/json',
+ processData: false,
+ data: JSON.stringify(bandInvitation)
+ });
+ }
+
function getSession(id) {
var url = "/api/sessions/" + id;
return $.ajax({
@@ -109,6 +161,13 @@
});
}
+ function getGenres(options) {
+ return $.ajax('/api/genres', {
+ data: { },
+ dataType: 'json'
+ });
+ }
+
function updateAvatar(options) {
var id = getId(options);
@@ -374,6 +433,7 @@
this.getCountries = getCountries;
this.getIsps = getIsps;
this.getInstruments = getInstruments;
+ this.getGenres = getGenres;
this.updateAvatar = updateAvatar;
this.deleteAvatar = deleteAvatar;
this.getFilepickerPolicy = getFilepickerPolicy;
@@ -396,6 +456,10 @@
this.stopRecording = stopRecording;
this.getRecording = getRecording;
this.putTrackSyncChange = putTrackSyncChange;
+ this.createBand = createBand;
+ this.updateBand = updateBand;
+ this.createBandInvitation = createBandInvitation;
+ this.updateBandInvitation = updateBandInvitation;
return this;
};
diff --git a/web/app/assets/javascripts/profile.js b/web/app/assets/javascripts/profile.js
index 17f07bff2..fb16c59bb 100644
--- a/web/app/assets/javascripts/profile.js
+++ b/web/app/assets/javascripts/profile.js
@@ -404,7 +404,7 @@
$.each(response, function(index, val) {
var template = $('#template-profile-social').html();
var followingHtml = context.JK.fillTemplate(template, {
- avatar_url: context.JK.resolveAvatarUrl(val.logo_url),
+ avatar_url: context.JK.resolveBandAvatarUrl(val.logo_url),
userName: val.name,
location: val.location
});
@@ -482,60 +482,80 @@
async: false,
processData:false,
success: function(response) {
- $.each(response, function(index, val) {
+ logger.debug("context.JK.currentUserId=" + context.JK.currentUserId);
+ logger.debug("userId=" + userId);
+ if ( (!response || response.length === 0) && context.JK.currentUserId === userId) {
+ var noBandHtml = $('#template-no-bands').html();
+ $("#profile-bands").append(noBandHtml);
+ }
- // build band member HTML
- var musicianHtml = '';
- if ("musicians" in val) {
- for (var i=0; i < val.musicians.length; i++) {
- var musician = val.musicians[i];
- var instrumentLogoHtml = '';
- if ("instruments" in musician) {
- for (var j=0; j < musician.instruments.length; j++) {
- var instrument = musician.instruments[j];
- var inst = '../assets/content/icon_instrument_default24.png';
- if (instrument.instrument_id in instrument_logo_map) {
- inst = instrument_logo_map[instrument.instrument_id];
+ else {
+ addMoreBandsLink();
+
+ $.each(response, function(index, val) {
+
+ // build band member HTML
+ var musicianHtml = '';
+ if ("musicians" in val) {
+ for (var i=0; i < val.musicians.length; i++) {
+ var musician = val.musicians[i];
+ var instrumentLogoHtml = '';
+ if ("instruments" in musician) {
+ for (var j=0; j < musician.instruments.length; j++) {
+ var instrument = musician.instruments[j];
+ var inst = '../assets/content/icon_instrument_default24.png';
+ if (instrument.instrument_id in instrument_logo_map) {
+ inst = instrument_logo_map[instrument.instrument_id];
+ }
+ instrumentLogoHtml += ' ';
}
- instrumentLogoHtml += '
';
}
+ // this template is in _findSession.html.erb
+ var musicianTemplate = $('#template-musician-info').html();
+ musicianHtml += context.JK.fillTemplate(musicianTemplate, {
+ avatar_url: context.JK.resolveAvatarUrl(musician.photo_url),
+ profile_url: "/#/profile/" + musician.id,
+ musician_name: musician.name,
+ instruments: instrumentLogoHtml
+ });
}
- // this template is in _findSession.html.erb
- var musicianTemplate = $('#template-musician-info').html();
- musicianHtml += context.JK.fillTemplate(musicianTemplate, {
- avatar_url: context.JK.resolveAvatarUrl(musician.photo_url),
- profile_url: "/#/profile/" + musician.id,
- musician_name: musician.name,
- instruments: instrumentLogoHtml
- });
}
- }
- var template = $('#template-profile-bands').html();
- var bandHtml = context.JK.fillTemplate(template, {
- bandId: val.id,
- biography: val.biography,
- band_url: "/#/bandProfile/" + val.id,
- avatar_url: context.JK.resolveAvatarUrl(val.logo_url),
- name: val.name,
- location: val.location,
- genres: formatGenres(val.genres),
- follower_count: val.follower_count,
- recording_count: val.recording_count,
- session_count: val.session_count,
- musicians: musicianHtml
- });
+ var template = $('#template-profile-bands').html();
+ var bandHtml = context.JK.fillTemplate(template, {
+ bandId: val.id,
+ biography: val.biography,
+ band_url: "/#/bandProfile/" + val.id,
+ avatar_url: context.JK.resolveBandAvatarUrl(val.logo_url),
+ name: val.name,
+ location: val.location,
+ genres: formatGenres(val.genres),
+ follower_count: val.follower_count,
+ recording_count: val.recording_count,
+ session_count: val.session_count,
+ musicians: musicianHtml
+ });
- $('#profile-bands').append(bandHtml);
+ $('#profile-bands').append(bandHtml);
- // wire up Band Follow button click handler
- var following = isFollowingBand(val.id);
- configureBandFollowingButton(following, val.id);
- });
+ // wire up Band Follow button click handler
+ var following = isFollowingBand(val.id);
+ configureBandFollowingButton(following, val.id);
+ });
+
+ addMoreBandsLink();
+ }
},
error: app.ajaxError
});
}
+ function addMoreBandsLink() {
+ if (context.JK.currentUserId === userId) {
+ var moreBandsHtml = $('#template-more-bands').html();
+ $("#profile-bands").append(moreBandsHtml);
+ }
+ }
+
function formatGenres(genres) {
var formattedGenres = '';
if (genres) {
diff --git a/web/app/assets/javascripts/utils.js b/web/app/assets/javascripts/utils.js
index d6ff1c289..8f676b221 100644
--- a/web/app/assets/javascripts/utils.js
+++ b/web/app/assets/javascripts/utils.js
@@ -85,6 +85,10 @@
return photo_url ? photo_url : "/assets/shared/avatar_generic.png";
};
+ context.JK.resolveBandAvatarUrl = function(photo_url) {
+ return photo_url ? photo_url : "/assets/shared/avatar_generic_band.png";
+ };
+
context.JK.getInstrumentIconMap24 = function() {
return instrumentIconMap24;
};
diff --git a/web/app/assets/stylesheets/client/bandProfile.css.scss b/web/app/assets/stylesheets/client/band.css.scss
similarity index 92%
rename from web/app/assets/stylesheets/client/bandProfile.css.scss
rename to web/app/assets/stylesheets/client/band.css.scss
index d1ab30b9c..c907ef7d5 100644
--- a/web/app/assets/stylesheets/client/bandProfile.css.scss
+++ b/web/app/assets/stylesheets/client/band.css.scss
@@ -1,5 +1,22 @@
@import "client/common.css.scss";
+.band-setup-bio {
+ height:90px;
+ overflow:auto;
+}
+
+.band-setup-genres {
+ width:100%;
+ height:90px;
+ background-color:#c5c5c5;
+ border:none;
+ -webkit-box-shadow: inset 2px 2px 3px 0px #888;
+ box-shadow: inset 2px 2px 3px 0px #888;
+ color:#666;
+ overflow:auto;
+ font-size:14px;
+}
+
.band-profile-header {
padding:20px;
height:120px;
diff --git a/web/app/assets/stylesheets/client/client.css b/web/app/assets/stylesheets/client/client.css
index 56ee1c869..2fb0e0a17 100644
--- a/web/app/assets/stylesheets/client/client.css
+++ b/web/app/assets/stylesheets/client/client.css
@@ -22,7 +22,7 @@
*= require ./sidebar
*= require ./home
*= require ./profile
- *= require ./bandProfile
+ *= require ./band
*= require ./findSession
*= require ./session
*= require ./account
diff --git a/web/app/assets/stylesheets/client/content.css.scss b/web/app/assets/stylesheets/client/content.css.scss
index 6a23a30a8..2c7d100e8 100644
--- a/web/app/assets/stylesheets/client/content.css.scss
+++ b/web/app/assets/stylesheets/client/content.css.scss
@@ -306,7 +306,7 @@ ul.shortcuts {
padding:2px;
}
- .account-home, .audio, .get-help, .download-app, .invite-friends {
+ .account-home, .band-setup, .audio, .get-help, .download-app, .invite-friends {
border-bottom:1px;
border-style:solid;
border-color:#ED3618;
diff --git a/web/app/assets/stylesheets/client/profile.css.scss b/web/app/assets/stylesheets/client/profile.css.scss
index 1c9bc1441..c7f543555 100644
--- a/web/app/assets/stylesheets/client/profile.css.scss
+++ b/web/app/assets/stylesheets/client/profile.css.scss
@@ -143,6 +143,22 @@
font-weight:600;
}
+#profile-bands .when-empty {
+ margin: 0px;
+ padding:0px;
+ display:block;
+ vertical-align:middle;
+ text-align:center;
+ font-weight: bold;
+ font-size: 120%;
+ line-height: 150%;
+}
+
+#profile-bands .when-empty a {
+ text-decoration: underline;
+ color: inherit;
+}
+
.profile-bands {
width:100%;
min-height:90px;
diff --git a/web/app/controllers/api_bands_controller.rb b/web/app/controllers/api_bands_controller.rb
index baf88d43c..ad75323da 100644
--- a/web/app/controllers/api_bands_controller.rb
+++ b/web/app/controllers/api_bands_controller.rb
@@ -16,7 +16,7 @@ class ApiBandsController < ApiController
end
def create
- @band = Band.save(params[:id],
+ @band = Band.save(nil,
params[:name],
params[:website],
params[:biography],
@@ -153,7 +153,7 @@ class ApiBandsController < ApiController
end
def invitation_create
- @invitation = BandInvitation.save(params[:invitation_id],
+ @invitation = BandInvitation.save(nil,
params[:id],
params[:user_id],
current_user.id,
diff --git a/web/app/views/clients/_bandProfile.html.erb b/web/app/views/clients/_bandProfile.html.erb
index 6228fd0a1..ef791def1 100644
--- a/web/app/views/clients/_bandProfile.html.erb
+++ b/web/app/views/clients/_bandProfile.html.erb
@@ -90,7 +90,7 @@
{biography}
PROFILE
FOLLOW
- CONNECT
+
diff --git a/web/app/views/clients/_band_setup.html.erb b/web/app/views/clients/_band_setup.html.erb
new file mode 100644
index 000000000..581178ef5
--- /dev/null
+++ b/web/app/views/clients/_band_setup.html.erb
@@ -0,0 +1,128 @@
+
+