Browse Source

Merge pull request #16 from Musare/staging

Beta Release Day 9
Jonathan 8 years ago
parent
commit
318774e3e8
69 changed files with 665 additions and 284 deletions
  1. 16 7
      backend/logic/actions/playlists.js
  2. 19 28
      backend/logic/actions/stations.js
  3. 39 18
      backend/logic/actions/users.js
  4. 1 0
      backend/logic/app.js
  5. 0 1
      backend/logic/db/schemas/playlist.js
  6. 1 0
      backend/logic/db/schemas/user.js
  7. 7 12
      backend/logic/stations.js
  8. 1 0
      backend/package.json
  9. 17 1
      frontend/App.vue
  10. BIN
      frontend/build/android-chrome-144x144.png
  11. BIN
      frontend/build/android-chrome-192x192.png
  12. BIN
      frontend/build/android-chrome-36x36.png
  13. BIN
      frontend/build/android-chrome-48x48.png
  14. BIN
      frontend/build/android-chrome-72x72.png
  15. BIN
      frontend/build/android-chrome-96x96.png
  16. BIN
      frontend/build/apple-touch-icon-114x114.png
  17. BIN
      frontend/build/apple-touch-icon-120x120.png
  18. BIN
      frontend/build/apple-touch-icon-144x144.png
  19. BIN
      frontend/build/apple-touch-icon-152x152.png
  20. BIN
      frontend/build/apple-touch-icon-180x180.png
  21. BIN
      frontend/build/apple-touch-icon-57x57.png
  22. BIN
      frontend/build/apple-touch-icon-60x60.png
  23. BIN
      frontend/build/apple-touch-icon-72x72.png
  24. BIN
      frontend/build/apple-touch-icon-76x76.png
  25. BIN
      frontend/build/apple-touch-icon-precomposed.png
  26. BIN
      frontend/build/apple-touch-icon.png
  27. BIN
      frontend/build/assets/favicon.ico
  28. 10 0
      frontend/build/assets/social/facebook.svg
  29. 59 47
      frontend/build/assets/social/github.svg
  30. 18 0
      frontend/build/assets/social/twitter.svg
  31. 12 0
      frontend/build/browserconfig.xml
  32. BIN
      frontend/build/favicon-16x16.png
  33. BIN
      frontend/build/favicon-194x194.png
  34. BIN
      frontend/build/favicon-32x32.png
  35. BIN
      frontend/build/favicon-96x96.png
  36. BIN
      frontend/build/favicon.ico
  37. 42 14
      frontend/build/index.html
  38. 41 0
      frontend/build/manifest.json
  39. BIN
      frontend/build/mstile-144x144.png
  40. BIN
      frontend/build/mstile-150x150.png
  41. BIN
      frontend/build/mstile-310x150.png
  42. BIN
      frontend/build/mstile-310x310.png
  43. BIN
      frontend/build/mstile-70x70.png
  44. 2 0
      frontend/build/robots.txt
  45. 116 0
      frontend/build/safari-pinned-tab.svg
  46. 5 3
      frontend/components/Admin/QueueSongs.vue
  47. 2 2
      frontend/components/Admin/Reports.vue
  48. 4 2
      frontend/components/Admin/Songs.vue
  49. 6 4
      frontend/components/Admin/Stations.vue
  50. 2 2
      frontend/components/MainFooter.vue
  51. 2 2
      frontend/components/MainHeader.vue
  52. 20 17
      frontend/components/Modals/AddSongToQueue.vue
  53. 2 2
      frontend/components/Modals/CreateCommunityStation.vue
  54. 38 23
      frontend/components/Modals/EditSong.vue
  55. 29 12
      frontend/components/Modals/EditStation.vue
  56. 2 2
      frontend/components/Modals/IssuesModal.vue
  57. 6 3
      frontend/components/Modals/Login.vue
  58. 1 1
      frontend/components/Modals/Playlists/Create.vue
  59. 21 22
      frontend/components/Modals/Playlists/Edit.vue
  60. 9 4
      frontend/components/Modals/Register.vue
  61. 4 2
      frontend/components/Modals/Report.vue
  62. 13 4
      frontend/components/Sidebars/Playlist.vue
  63. 4 2
      frontend/components/Sidebars/Queue.vue
  64. 1 1
      frontend/components/Sidebars/UsersList.vue
  65. 67 8
      frontend/components/Station/Station.vue
  66. 6 4
      frontend/components/User/Settings.vue
  67. 2 2
      frontend/components/User/Show.vue
  68. 18 29
      frontend/components/pages/Home.vue
  69. 0 3
      frontend/io.js

+ 16 - 7
backend/logic/actions/playlists.js

@@ -71,6 +71,16 @@ cache.sub('playlist.updateDisplayName', res => {
 
 let lib = {
 
+	getFirstSong: hooks.loginRequired((session, playlistId, cb, userId) => {
+		playlists.getPlaylist(playlistId, (err, playlist) => {
+			if (err || !playlist || playlist.createdBy !== userId) return cb({ status: 'failure', message: 'Something went wrong when getting the playlist'});
+			cb({
+				status: 'success',
+				song: playlist.songs[0]
+			});
+		});
+	}),
+
 	indexForUser: hooks.loginRequired((session, cb, userId) => {
 		db.models.playlist.find({ createdBy: userId }, (err, playlists) => {
 			if (err) return cb({ status: 'failure', message: 'Something went wrong when getting the playlists'});;
@@ -99,6 +109,7 @@ let lib = {
 			}
 
 		], (err, playlist) => {
+			console.log(err, playlist);
 			if (err) return cb({ 'status': 'failure', 'message': 'Something went wrong'});
 			cache.pub('playlist.create', playlist._id);
 			return cb({ 'status': 'success', 'message': 'Successfully created playlist' });
@@ -135,11 +146,9 @@ let lib = {
 
 					let found = false;
 					playlist.songs.forEach((song) => {
-						if (songId === song._id) {
-							found = true;
-						}
+						if (songId === song._id) found = true;
 					});
-					if (found) return next('That song is already in the playlist.');
+					if (found) return next('That song is already in the playlist');
 					return next(null);
 				});
 			},
@@ -174,7 +183,7 @@ let lib = {
 		(err, playlist, newSong) => {
 			if (err) return cb({ status: 'error', message: err });
 			else if (playlist.songs) {
-				cache.pub('playlist.addSong', {playlistId: playlist._id, song: newSong, userId: userId});
+				cache.pub('playlist.addSong', { playlistId: playlist._id, song: newSong, userId: userId });
 				return cb({ status: 'success', message: 'Song has been successfully added to the playlist', data: playlist.songs });
 			}
 		});
@@ -307,8 +316,8 @@ let lib = {
 						console.log(err);
 						if (err) return cb({status: 'failure', message: 'Something went wrong when moving the song'});
 						playlists.updatePlaylist(playlistId, (err) => {
-							if (err) return cb({ status: 'failure', message: err});
-							cache.pub('playlist.moveSongToBottom', {playlistId, songId, userId: userId});
+							if (err) return cb({ status: 'failure', message: err });
+							cache.pub('playlist.moveSongToBottom', { playlistId, songId, userId: userId });
 							return cb({ status: 'success', message: 'Playlist has been successfully updated' });
 						})
 					});

+ 19 - 28
backend/logic/actions/stations.js

@@ -27,15 +27,13 @@ cache.sub('station.pause', stationId => {
 
 cache.sub('station.resume', stationId => {
 	stations.getStation(stationId, (err, station) => {
-		utils.emitToRoom(`station.${stationId}`, "event:stations.resume", {timePaused: station.timePaused});
+		utils.emitToRoom(`station.${stationId}`, "event:stations.resume", { timePaused: station.timePaused });
 	});
 });
 
 cache.sub('station.queueUpdate', stationId => {
 	stations.getStation(stationId, (err, station) => {
-		if (!err) {
-			utils.emitToRoom(`station.${stationId}`, "event:queue.update", station.queue);
-		}
+		if (!err) utils.emitToRoom(`station.${stationId}`, "event:queue.update", station.queue);
 	});
 });
 
@@ -376,30 +374,25 @@ module.exports = {
 
 	resume: hooks.ownerRequired((session, stationId, cb) => {
 		stations.getStation(stationId, (err, station) => {
-			if (err && err !== true) {
-				return cb({ status: 'error', message: 'An error occurred while resuming the station' });
-			} else if (station) {
+			if (err && err !== true) return cb({ status: 'error', message: 'An error occurred while resuming the station' });
+			else if (station) {
 				if (station.paused) {
 					station.paused = false;
 					station.timePaused += (Date.now() - station.pausedAt);
-					db.models.station.update({_id: stationId}, {$set: {paused: false}, $inc: {timePaused: Date.now() - station.pausedAt}}, () => {
+					db.models.station.update({ _id: stationId }, { $set: { paused: false }, $inc: { timePaused: Date.now() - station.pausedAt } }, () => {
 						stations.updateStation(stationId, (err, station) => {
 							cache.pub('station.resume', stationId);
 							cb({ status: 'success' });
 						});
 					});
-				} else {
-					cb({ status: 'failure', message: 'That station is not paused.' });
-				}
-			} else {
-				cb({ status: 'failure', message: `That station doesn't exist, it may have been deleted` });
-			}
+				} else cb({ status: 'failure', message: 'That station is not paused.' });
+			} else cb({ status: 'failure', message: `That station doesn't exist, it may have been deleted` });
 		});
 	}),
 
 	remove: hooks.ownerRequired((session, stationId, cb) => {
 		db.models.station.remove({ _id: stationId }, (err) => {
-			if (err) return cb({status: 'failure', message: 'Something went wrong when deleting that station.'});
+			if (err) return cb({ status: 'failure', message: 'Something went wrong when deleting that station' });
 			cache.hdel('stations', stationId, () => {
 				cache.pub('station.remove', stationId);
 				return cb({ status: 'success', message: 'Station successfully removed' });
@@ -473,13 +466,11 @@ module.exports = {
 			if (err) return cb(err);
 			if (station.type === 'community') {
 				let has = false;
-				station.queue.forEach((queueSong) => {
-					if (queueSong._id === songId) {
-						has = true;
-					}
+				station.queue.forEach(queueSong => {
+					if (queueSong._id === songId) has = true;
 				});
-				if (has) return cb({'status': 'failure', 'message': 'That song has already been added to the queue.'});
-				if (station.currentSong && station.currentSong._id === songId) return cb({'status': 'failure', 'message': 'That song is currently playing.'});
+				if (has) return cb({'status': 'failure', 'message': 'That song has already been added to the queue'});
+				if (station.currentSong && station.currentSong._id === songId) return cb({'status': 'failure', 'message': 'That song is currently playing'});
 
 				songs.getSong(songId, (err, song) => {
 					if (err) {
@@ -494,17 +485,17 @@ module.exports = {
 						});
 					} else cont(song);
 					function cont(song) {
-						db.models.station.update({_id: stationId}, {$push: {queue: song}}, (err) => {
-							if (err) return cb({'status': 'failure', 'message': 'Something went wrong.'});
+						db.models.station.update({ _id: stationId }, { $push: { queue: song } }, (err) => {
+							if (err) return cb({'status': 'failure', 'message': 'Something went wrong'});
 							stations.updateStation(stationId, (err, station) => {
 								if (err) return cb(err);
 								cache.pub('station.queueUpdate', stationId);
-								cb({'status': 'success', 'message': 'Added that song to the queue.'});
+								cb({ 'status': 'success', 'message': 'Added that song to the queue' });
 							});
 						});
 					}
 				});
-			} else cb({'status': 'failure', 'message': 'That station is not a community station.'});
+			} else cb({'status': 'failure', 'message': 'That station is not a community station'});
 		});
 	}),
 
@@ -545,14 +536,14 @@ module.exports = {
 			if (err) return cb(err);
 			if (station.type === 'community') {
 				if (station.privatePlaylist === playlistId) return cb({'status': 'failure', 'message': 'That playlist is already selected.'});
-				db.models.playlist.findOne({_id: playlistId}, (err, playlist) => {
+				db.models.playlist.findOne({ _id: playlistId }, (err, playlist) => {
 					if (err) return cb(err);
 					if (playlist) {
-						db.models.station.update({_id: stationId}, {$set: {privatePlaylist: playlistId, currentSongIndex: 0}}, (err) => {
+						db.models.station.update({_id: stationId}, { $set: { privatePlaylist: playlistId, currentSongIndex: 0 } }, (err) => {
 							if (err) return cb(err);
 							stations.updateStation(stationId, (err, station) => {
 								if (err) return cb(err);
-								stations.skipStation(stationId)();
+								if (!station.partyMode) stations.skipStation(stationId)();
 								cache.pub('privatePlaylist.selected', {playlistId, stationId});
 								cb({'status': 'success', 'message': 'Playlist selected.'});
 							});

+ 39 - 18
backend/logic/actions/users.js

@@ -9,6 +9,15 @@ const db = require('../db');
 const cache = require('../cache');
 const utils = require('../utils');
 const hooks = require('./hooks');
+const sha256 = require('sha256');
+
+cache.sub('user.updateUsername', user => {
+	utils.socketsFromUser(user._id, sockets => {
+		sockets.forEach(socket => {
+			socket.emit('event:user.username.changed', user.username);
+		});
+	});
+});
 
 module.exports = {
 
@@ -26,8 +35,9 @@ module.exports = {
 			// if the user doesn't exist, respond with a failure
 			// otherwise compare the requested password and the actual users password
 			(user, next) => {
-				if (!user) return next(true, { status: 'failure', message: 'User not found' });
-				bcrypt.compare(password, user.services.password.password, (err, match) => {
+				if (!user) return next('User not found');
+				if (!user.services.password || !user.services.password.password) return next('The account you are trying to access uses GitHub to log in.');
+				bcrypt.compare(sha256(password), user.services.password.password, (err, match) => {
 
 					if (err) return next(err);
 
@@ -55,8 +65,9 @@ module.exports = {
 
 			// log this error somewhere
 			if (err && err !== true) {
-				console.error(err);
-				return cb({ status: 'error', message: 'An error occurred while logging in' });
+				if (typeof err === "string") return cb({ status: 'error', message: err });
+				else if (err.message) return cb({ status: 'error', message: err.message });
+				else return cb({ status: 'error', message: 'An error occurred.' });
 			}
 
 			cb(payload);
@@ -84,32 +95,33 @@ module.exports = {
 			// if it is, we check if a user with the requested username already exists
 			(response, body, next) => {
 				let json = JSON.parse(body);
-				if (json.success !== true) return next('Response from recaptcha was not successful');
+				if (json.success !== true) return next('Response from recaptcha was not successful.');
 				db.models.user.findOne({ username: new RegExp(`^${username}$`, 'i') }, next);
 			},
 
 			// if the user already exists, respond with that
 			// otherwise check if a user with the requested email already exists
 			(user, next) => {
-				if (user) return next(true, { status: 'failure', message: 'A user with that username already exists' });
+				if (user) return next('A user with that username already exists.');
 				db.models.user.findOne({ 'email.address': email }, next);
 			},
 
 			// if the user already exists, respond with that
 			// otherwise, generate a salt to use with hashing the new users password
 			(user, next) => {
-				if (user) return next(true, { status: 'failure', message: 'A user with that email already exists' });
+				if (user) return next('A user with that email already exists.');
 				bcrypt.genSalt(10, next);
 			},
 
 			// hash the password
 			(salt, next) => {
-				bcrypt.hash(password, salt, next)
+				bcrypt.hash(sha256(password), salt, next)
 			},
 
 			// save the new user to the database
 			(hash, next) => {
 				db.models.user.create({
+					_id: utils.generateRandomString(12),//TODO Check if exists
 					username,
 					email: {
 						address: email,
@@ -132,8 +144,9 @@ module.exports = {
 		], (err, payload) => {
 			// log this error somewhere
 			if (err && err !== true) {
-				console.error(err);
-				return cb({ status: 'error', message: 'An error occurred while registering for an account' });
+				if (typeof err === "string") return cb({ status: 'error', message: err });
+				else if (err.message) return cb({ status: 'error', message: err.message });
+				else return cb({ status: 'error', message: 'An error occurred.' });
 			} else {
 				module.exports.login(session, email, password, (result) => {
 					let obj = {status: 'success', message: 'Successfully registered.'};
@@ -208,24 +221,32 @@ module.exports = {
 	updateUsername: hooks.loginRequired((session, newUsername, cb, userId) => {
 		db.models.user.findOne({ _id: userId }, (err, user) => {
 			if (err) console.error(err);
-			if (!user) return cb({ status: 'error', message: 'User not found.' });
+			if (!user) return cb({ status: 'error', message: 'User not found' });
 			if (user.username !== newUsername) {
 				if (user.username.toLowerCase() !== newUsername.toLowerCase()) {
-					db.models.user.findOne({username: new RegExp(`^${newUsername}$`, 'i')}, (err, _user) => {
+					db.models.user.findOne({ username: new RegExp(`^${newUsername}$`, 'i') }, (err, _user) => {
 						if (err) return cb({ status: 'error', message: err.message });
-						if (_user) return cb({ status: 'failure', message: 'That username is already in use.' });
-						db.models.user.update({_id: userId}, {$set: {username: newUsername}}, (err) => {
+						if (_user) return cb({ status: 'failure', message: 'That username is already in use' });
+						db.models.user.update({ _id: userId }, { $set: { username: newUsername } }, (err) => {
 							if (err) return cb({ status: 'error', message: err.message });
-							cb({ status: 'success', message: 'Username updated successfully.' });
+							cache.pub('user.updateUsername', {
+								username: newUsername,
+								_id: userId
+							});
+							cb({ status: 'success', message: 'Username updated successfully' });
 						});
 					});
 				} else {
-					db.models.user.update({_id: userId}, {$set: {username: newUsername}}, (err) => {
+					db.models.user.update({ _id: userId }, { $set: { username: newUsername } }, (err) => {
 						if (err) return cb({ status: 'error', message: err.message });
-						cb({ status: 'success', message: 'Username updated successfully.' });
+						cache.pub('user.updateUsername', {
+							username: newUsername,
+							_id: userId
+						});
+						cb({ status: 'success', message: 'Username updated successfully' });
 					});
 				}
-			} else cb({ status: 'error', message: 'Username has not changed. Your new username cannot be the same as your old username.' });
+			} else cb({ status: 'error', message: 'Your new username cannot be the same as your old username' });
 		});
 	}),
 

+ 1 - 0
backend/logic/app.js

@@ -100,6 +100,7 @@ const lib = {
 											if (err) return redirectOnErr(res, err.message);
 											if (user) return redirectOnErr(res, 'An account with that email address already exists.');
 											else db.models.user.create({
+												_id: utils.generateRandomString(12),//TODO Check if exists
 												username: body.login,
 												email: {
 													address,

+ 0 - 1
backend/logic/db/schemas/playlist.js

@@ -1,5 +1,4 @@
 module.exports = {
-	name: { type: String, lowercase: true, max: 16, min: 2 },
 	displayName: { type: String, min: 2, max: 32, required: true },
 	songs: { type: Array },
 	createdBy: { type: String, required: true },

+ 1 - 0
backend/logic/db/schemas/user.js

@@ -1,4 +1,5 @@
 module.exports = {
+	_id: { type: String, required: true, index: true, unique: true, min: 12, max: 12 },
 	username: { type: String, required: true },
 	role: { type: String, default: 'default', required: true },
 	email: {

+ 7 - 12
backend/logic/stations.js

@@ -75,7 +75,7 @@ module.exports = {
 						notifications.unschedule(`stations.nextSong?id${station._id}`);
 						cb(null, station);
 					}
-				} else cb("Station not found.");
+				} else cb("Station not found");
 			} else cb(err);
 		});
 	},
@@ -163,7 +163,7 @@ module.exports = {
 			},
 
 			(station, next) => {
-				if (!station) return next('Station not found.');
+				if (!station) return next('Station not found');
 				cache.hset('stations', stationId, station, (err) => {
 					if (err) return next(err);
 					next(null, station);
@@ -289,23 +289,18 @@ module.exports = {
 									});
 								}
 							} else {
-								if (station.partyMode === true) {
-									if (station.queue.length > 0) {
-										db.models.station.update({_id: stationId}, {$pull: {queue: {songId: station.queue[0]._id}}}, (err) => {
+								if (station.partyMode === true) if (station.queue.length > 0) {
+										db.models.station.update({ _id: stationId }, { $pull: { queue: { _id: station.queue[0]._id } } }, (err) => {
 											if (err) return next(err);
 											let $set = {};
 											$set.currentSong = station.queue[0];
 											$set.startedAt = Date.now();
 											$set.timePaused = 0;
-											if (station.paused) {
-												$set.pausedAt = Date.now();
-											}
+											if (station.paused) $set.pausedAt = Date.now();
 											next(null, $set);
 										});
-									} else {
-										next(null, {currentSong: null});
-									}
-								} else {
+									} else next(null, {currentSong: null});
+								else {
 									db.models.playlist.findOne({_id: station.privatePlaylist}, (err, playlist) => {
 										if (err || !playlist) return next(null, {currentSong: null});
 										playlist = playlist.songs;

+ 1 - 0
backend/package.json

@@ -30,6 +30,7 @@
     "passport.socketio": "^3.7.0",
     "redis": "^2.6.3",
     "request": "^2.74.0",
+    "sha256": "^0.2.0",
     "socket.io": "^1.5.0"
   }
 }

+ 17 - 1
frontend/App.vue

@@ -46,9 +46,11 @@
 		},
 		methods: {
 			logout: function () {
-				this.socket.emit('users.logout', result => {
+				let _this = this;
+				_this.socket.emit('users.logout', result => {
 					if (result.status === 'success') {
 						document.cookie = 'SID=;expires=Thu, 01 Jan 1970 00:00:01 GMT;';
+						_this.$router.go('/');
 						location.reload();
 					} else Toast.methods.addToast(result.message, 4000);
 				});
@@ -136,6 +138,14 @@
 <style type='scss'>
 	#toast-container { z-index: 10000 !important; }
 
+	.absolute-a {
+		width: 100%;
+		height: 100%;
+		position: absolute;
+		top: 0;
+		left: 0;
+	}
+
 	.socketNotConnected {
 		padding: 20px;
 		color: white;
@@ -217,4 +227,10 @@
 			&:after { left: 200%; }
 		}
 	}
+
+	.button:focus, .button:active { border-color: #dbdbdb !important; }
+	.input:focus, .input:active { border-color: #03a9f4 !important; }
+	button.delete:focus { background-color: rgba(10, 10, 10, 0.3); }
+
+	.tag { padding-right: 6px !important; }
 </style>

BIN
frontend/build/android-chrome-144x144.png


BIN
frontend/build/android-chrome-192x192.png


BIN
frontend/build/android-chrome-36x36.png


BIN
frontend/build/android-chrome-48x48.png


BIN
frontend/build/android-chrome-72x72.png


BIN
frontend/build/android-chrome-96x96.png


BIN
frontend/build/apple-touch-icon-114x114.png


BIN
frontend/build/apple-touch-icon-120x120.png


BIN
frontend/build/apple-touch-icon-144x144.png


BIN
frontend/build/apple-touch-icon-152x152.png


BIN
frontend/build/apple-touch-icon-180x180.png


BIN
frontend/build/apple-touch-icon-57x57.png


BIN
frontend/build/apple-touch-icon-60x60.png


BIN
frontend/build/apple-touch-icon-72x72.png


BIN
frontend/build/apple-touch-icon-76x76.png


BIN
frontend/build/apple-touch-icon-precomposed.png


BIN
frontend/build/apple-touch-icon.png


BIN
frontend/build/assets/favicon.ico


+ 10 - 0
frontend/build/assets/social/facebook.svg

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="-183 277 245 240" style="enable-background:new -183 277 245 240;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#4A4A4A;}
+</style>
+<path id="f" class="st0" d="M-48.5,474.3v-70.6h23.7l3.5-27.5h-27.2v-17.6c0-8,2.2-13.4,13.6-13.4l14.6,0v-24.6
+	c-2.5-0.3-11.2-1.1-21.2-1.1c-21,0-35.4,12.8-35.4,36.4v20.3h-23.7v27.5h23.7v70.6H-48.5z"/>
+</svg>

+ 59 - 47
frontend/build/assets/social/github.svg

@@ -1,47 +1,59 @@
-<svg width="245" height="240" xmlns="http://www.w3.org/2000/svg">
- <metadata id="metadata3362">image/svg+xml</metadata>
-
- <g>
-  <title>background</title>
-  <rect fill="none" id="canvas_background" height="402" width="582" y="-1" x="-1"/>
- </g>
- <g>
-  <title>Layer 1</title>
-  <g id="svg_86">
-   <metadata fill="#4a4a4a" transform="matrix(1.1862952709197998,0,0,1.1862952709197998,27.944357648753794,19.726211432238415) " id="svg_85">image/svg+xml</metadata>
-   <defs fill="#4a4a4a">
-    <clipPath fill="#4a4a4a" clipPathUnits="userSpaceOnUse" id="svg_83">
-     <path fill="#4a4a4a" d="m0,551.986l530.973,0l0,-551.986l-530.973,0l0,551.986z" id="svg_84"/>
-    </clipPath>
-   </defs>
-   <g transform="matrix(1.1862952709197998,0,0,1.1862952709197998,27.944357648753794,19.726211432238415) " id="svg_66">
-    <title fill="#4a4a4a">Layer 1</title>
-    <g transform="matrix(1.3333333,0,0,-1.3333333,0,735.98133) " id="svg_81">
-     <path fill="#4a4a4a" fill-rule="evenodd" d="m60.38802,551.98602c-33.347,0 -60.388,-27.035 -60.388,-60.388c0,-26.68 17.303,-49.316 41.297,-57.301c3.018,-0.559 4.126,1.31 4.126,2.905c0,1.439 -0.056,6.197 -0.082,11.243c-16.8,-3.653 -20.345,7.125 -20.345,7.125c-2.747,6.979 -6.705,8.836 -6.705,8.836c-5.479,3.748 0.413,3.671 0.413,3.671c6.064,-0.426 9.257,-6.224 9.257,-6.224c5.386,-9.231 14.127,-6.562 17.573,-5.019c0.543,3.902 2.107,6.567 3.834,8.075c-13.413,1.526 -27.513,6.705 -27.513,29.844c0,6.592 2.359,11.98 6.222,16.209c-0.627,1.521 -2.694,7.663 0.586,15.981c0,0 5.071,1.622 16.61,-6.191c4.817,1.338 9.983,2.009 15.115,2.033c5.132,-0.024 10.302,-0.695 15.128,-2.033c11.526,7.813 16.59,6.191 16.59,6.191c3.287,-8.318 1.22,-14.46 0.593,-15.981c3.872,-4.229 6.214,-9.617 6.214,-16.209c0,-23.195 -14.127,-28.301 -27.574,-29.796c2.166,-1.874 4.096,-5.549 4.096,-11.183c0,-8.08 -0.069,-14.583 -0.069,-16.572c0,-1.608 1.086,-3.49 4.147,-2.898c23.982,7.994 41.263,30.622 41.263,57.294c0,33.353 -27.037,60.388 -60.388,60.388" id="svg_82"/>
-    </g>
-    <g transform="matrix(1.3333333,0,0,-1.3333333,0,735.98133) " id="svg_79">
-     <path fill="#4a4a4a" fill-rule="nonzero" d="m22.87242,465.28284c-0.133,-0.301 -0.605,-0.391 -1.035,-0.185c-0.439,0.198 -0.684,0.607 -0.542,0.908c0.13,0.308 0.602,0.394 1.04,0.188c0.438,-0.197 0.688,-0.61 0.537,-0.911" id="svg_80"/>
-    </g>
-    <g transform="matrix(1.3333333,0,0,-1.3333333,0,735.98133) " id="svg_77">
-     <path fill="#4a4a4a" fill-rule="nonzero" d="m25.31871,462.55432c-0.288,-0.267 -0.852,-0.143 -1.233,0.279c-0.396,0.421 -0.469,0.985 -0.177,1.255c0.297,0.267 0.843,0.142 1.238,-0.279c0.396,-0.426 0.473,-0.984 0.172,-1.255" id="svg_78"/>
-    </g>
-    <g transform="matrix(1.3333333,0,0,-1.3333333,0,735.98133) " id="svg_75">
-     <path fill="#4a4a4a" fill-rule="nonzero" d="m27.69963,459.07684c-0.37,-0.258 -0.976,-0.017 -1.35,0.52c-0.37,0.538 -0.37,1.182 0.009,1.44c0.374,0.258 0.971,0.025 1.35,-0.507c0.369,-0.546 0.369,-1.19 -0.009,-1.453" id="svg_76"/>
-    </g>
-    <g transform="matrix(1.3333333,0,0,-1.3333333,0,735.98133) " id="svg_73">
-     <path fill="#4a4a4a" fill-rule="nonzero" d="m30.96132,455.71643c-0.331,-0.365 -1.036,-0.267 -1.552,0.232c-0.528,0.486 -0.675,1.177 -0.344,1.542c0.336,0.366 1.045,0.263 1.565,-0.231c0.524,-0.486 0.683,-1.182 0.331,-1.543" id="svg_74"/>
-    </g>
-    <g transform="matrix(1.3333333,0,0,-1.3333333,0,735.98133) " id="svg_71">
-     <path fill="#4a4a4a" fill-rule="nonzero" d="m35.46132,453.76535c-0.147,-0.473 -0.825,-0.687 -1.509,-0.486c-0.683,0.207 -1.13,0.76 -0.992,1.238c0.142,0.476 0.824,0.7 1.513,0.485c0.682,-0.206 1.13,-0.756 0.988,-1.237" id="svg_72"/>
-    </g>
-    <g transform="matrix(1.3333333,0,0,-1.3333333,0,735.98133) " id="svg_69">
-     <path fill="#4a4a4a" fill-rule="nonzero" d="m40.40373,453.40393c0.017,-0.498 -0.563,-0.911 -1.281,-0.92c-0.722,-0.016 -1.307,0.387 -1.315,0.877c0,0.503 0.568,0.911 1.289,0.924c0.718,0.014 1.307,-0.387 1.307,-0.881" id="svg_70"/>
-    </g>
-    <g transform="matrix(1.3333333,0,0,-1.3333333,0,735.98133) " id="svg_67">
-     <path fill="#4a4a4a" fill-rule="nonzero" d="m45.00234,454.18625c0.086,-0.485 -0.413,-0.984 -1.126,-1.117c-0.701,-0.129 -1.35,0.172 -1.439,0.653c-0.087,0.498 0.42,0.997 1.121,1.126c0.714,0.124 1.353,-0.168 1.444,-0.662" id="svg_68"/>
-    </g>
-   </g>
-  </g>
-  <g id="svg_22"/>
- </g>
-</svg>
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="-183 277 245 240" style="enable-background:new -183 277 245 240;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:none;}
+	.st1{fill:#4A4A4A;}
+</style>
+<g>
+	<title>background</title>
+	<rect id="canvas_background" x="-184" y="276" class="st0" width="582" height="402"/>
+</g>
+<g>
+	<title>Layer 1</title>
+	<g id="svg_86">
+		<g id="svg_66" transform="matrix(1.1862952709197998,0,0,1.1862952709197998,27.944357648753794,19.726211432238415) ">
+			<title  fill="#4a4a4a">Layer 1</title>
+			<g id="svg_81" transform="matrix(1.3333333,0,0,-1.3333333,0,735.98133) ">
+				<path id="svg_82" class="st1" d="M-55.3,376.9c-33.3,0-60.4-27-60.4-60.4c0-26.7,17.3-49.3,41.3-57.3c3-0.6,4.1,1.3,4.1,2.9
+					c0,1.4-0.1,6.2-0.1,11.2c-16.8-3.7-20.3,7.1-20.3,7.1c-2.7,7-6.7,8.8-6.7,8.8c-5.5,3.7,0.4,3.7,0.4,3.7c6.1-0.4,9.3-6.2,9.3-6.2
+					c5.4-9.2,14.1-6.6,17.6-5c0.5,3.9,2.1,6.6,3.8,8.1c-13.4,1.5-27.5,6.7-27.5,29.8c0,6.6,2.4,12,6.2,16.2c-0.6,1.5-2.7,7.7,0.6,16
+					c0,0,5.1,1.6,16.6-6.2c4.8,1.3,10,2,15.1,2c5.1,0,10.3-0.7,15.1-2c11.5,7.8,16.6,6.2,16.6,6.2c3.3-8.3,1.2-14.5,0.6-16
+					c3.9-4.2,6.2-9.6,6.2-16.2c0-23.2-14.1-28.3-27.6-29.8c2.2-1.9,4.1-5.5,4.1-11.2c0-8.1-0.1-14.6-0.1-16.6c0-1.6,1.1-3.5,4.1-2.9
+					c24,8,41.3,30.6,41.3,57.3C5.1,349.8-22,376.9-55.3,376.9"/>
+			</g>
+			<g id="svg_79" transform="matrix(1.3333333,0,0,-1.3333333,0,735.98133) ">
+				<path id="svg_80" class="st1" d="M-92.8,290.2c-0.1-0.3-0.6-0.4-1-0.2c-0.4,0.2-0.7,0.6-0.5,0.9c0.1,0.3,0.6,0.4,1,0.2
+					C-92.9,290.9-92.7,290.5-92.8,290.2"/>
+			</g>
+			<g id="svg_77" transform="matrix(1.3333333,0,0,-1.3333333,0,735.98133) ">
+				<path id="svg_78" class="st1" d="M-90.4,287.4c-0.3-0.3-0.9-0.1-1.2,0.3c-0.4,0.4-0.5,1-0.2,1.3c0.3,0.3,0.8,0.1,1.2-0.3
+					C-90.2,288.3-90.1,287.7-90.4,287.4"/>
+			</g>
+			<g id="svg_75" transform="matrix(1.3333333,0,0,-1.3333333,0,735.98133) ">
+				<path id="svg_76" class="st1" d="M-88,284c-0.4-0.3-1,0-1.3,0.5c-0.4,0.5-0.4,1.2,0,1.4c0.4,0.3,1,0,1.4-0.5
+					C-87.6,284.9-87.6,284.2-88,284"/>
+			</g>
+			<g id="svg_73" transform="matrix(1.3333333,0,0,-1.3333333,0,735.98133) ">
+				<path id="svg_74" class="st1" d="M-84.7,280.6c-0.3-0.4-1-0.3-1.6,0.2c-0.5,0.5-0.7,1.2-0.3,1.5c0.3,0.4,1,0.3,1.6-0.2
+					C-84.5,281.6-84.4,281-84.7,280.6"/>
+			</g>
+			<g id="svg_71" transform="matrix(1.3333333,0,0,-1.3333333,0,735.98133) ">
+				<path id="svg_72" class="st1" d="M-80.2,278.6c-0.1-0.5-0.8-0.7-1.5-0.5c-0.7,0.2-1.1,0.8-1,1.2c0.1,0.5,0.8,0.7,1.5,0.5
+					C-80.5,279.7-80.1,279.1-80.2,278.6"/>
+			</g>
+			<g id="svg_69" transform="matrix(1.3333333,0,0,-1.3333333,0,735.98133) ">
+				<path id="svg_70" class="st1" d="M-75.3,278.3c0-0.5-0.6-0.9-1.3-0.9c-0.7,0-1.3,0.4-1.3,0.9c0,0.5,0.6,0.9,1.3,0.9
+					C-75.9,279.2-75.3,278.8-75.3,278.3"/>
+			</g>
+			<g id="svg_67" transform="matrix(1.3333333,0,0,-1.3333333,0,735.98133) ">
+				<path id="svg_68" class="st1" d="M-70.7,279.1c0.1-0.5-0.4-1-1.1-1.1c-0.7-0.1-1.3,0.2-1.4,0.7c-0.1,0.5,0.4,1,1.1,1.1
+					C-71.4,279.8-70.8,279.6-70.7,279.1"/>
+			</g>
+		</g>
+	</g>
+	<g id="svg_22">
+	</g>
+</g>
+</svg>

+ 18 - 0
frontend/build/assets/social/twitter.svg

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="-183 277 245 240" style="enable-background:new -183 277 245 240;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:none;}
+	.st1{fill:#4A4A4A;}
+</style>
+<g>
+	<title>background</title>
+	<rect id="canvas_background" x="-184" y="276" class="st0" width="582" height="402"/>
+</g>
+<path class="st1" d="M-93.8,469.9c67.7,0,104.8-56.1,104.8-104.8c0-1.6,0-3.2-0.1-4.8c7.2-5.2,13.4-11.7,18.4-19.1
+	c-6.7,3-13.8,4.9-21.1,5.8c7.7-4.6,13.4-11.8,16.2-20.4c-7.2,4.3-15.1,7.3-23.4,8.9c-13.9-14.8-37.3-15.5-52.1-1.6
+	c-9.6,9-13.6,22.4-10.7,35.2c-29.6-1.5-57.2-15.5-75.9-38.5c-9.8,16.8-4.8,38.3,11.4,49.1c-5.9-0.2-11.6-1.8-16.7-4.6
+	c0,0.2,0,0.3,0,0.5c0,17.5,12.4,32.6,29.5,36.1c-5.4,1.5-11.1,1.7-16.6,0.6c4.8,15,18.6,25.3,34.4,25.6
+	c-13,10.2-29.1,15.8-45.7,15.8c-2.9,0-5.9-0.2-8.8-0.5C-133.4,464.2-113.8,469.9-93.8,469.9"/>
+</svg>

+ 12 - 0
frontend/build/browserconfig.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<browserconfig>
+  <msapplication>
+    <tile>
+      <square70x70logo src="/mstile-70x70.png?v=06042016"/>
+      <square150x150logo src="/mstile-150x150.png?v=06042016"/>
+      <square310x310logo src="/mstile-310x310.png?v=06042016"/>
+      <wide310x150logo src="/mstile-310x150.png?v=06042016"/>
+      <TileColor>#2d89ef</TileColor>
+    </tile>
+  </msapplication>
+</browserconfig>

BIN
frontend/build/favicon-16x16.png


BIN
frontend/build/favicon-194x194.png


BIN
frontend/build/favicon-32x32.png


BIN
frontend/build/favicon-96x96.png


BIN
frontend/build/favicon.ico


+ 42 - 14
frontend/build/index.html

@@ -1,22 +1,50 @@
 <!DOCTYPE html>
-<html lang="en">
+<html lang='en'>
 <head>
-	<meta charset="UTF-8">
-	<meta name="viewport" content="width=device-width, initial-scale=1">
 	<title>Musare</title>
-	<link rel="shortcut icon" type="image/x-icon" href="https://musare.com/favicon.ico" />
-	<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.3/css/font-awesome.min.css" rel="stylesheet" type="text/css">
-	<link href="https://fonts.googleapis.com/css?family=Roboto:100,400" rel="stylesheet">
-	<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
-	<link rel="stylesheet" href="/index.css">
-	<script src="https://www.youtube.com/iframe_api"></script>
-	<script src="/vendor/jquery.min.js"></script>
-	<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.15.0/moment.min.js"></script>
-	<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/1.4.8/socket.io.min.js"></script>
-	<script type="text/javascript" src="https://cdn.rawgit.com/atjonathan/lofig/master/dist/lofig.min.js"></script>
+
+	<meta charset='UTF-8'>
+	<meta http-equiv='X-UA-Compatible' content='IE=edge'>
+	<meta name='viewport' content='width=device-width, initial-scale=1'>
+	<meta name='keywords' content='music, musare, listen, station, station, radio, edm, chill, community, official, rooms, room, party, good, mus, pop'>
+	<meta name='description' content='On Musare you can listen to lots of different songs, playing 24/7 in our official stations and in user-made community stations!'>
+	<meta name='copyright' content='© Copyright Musare 2015-2016 All Right Reserved'>
+
+	<link rel='apple-touch-icon' sizes='57x57' href='/apple-touch-icon-57x57.png?v=06042016'>
+	<link rel='apple-touch-icon' sizes='60x60' href='/apple-touch-icon-60x60.png?v=06042016'>
+	<link rel='apple-touch-icon' sizes='72x72' href='/apple-touch-icon-72x72.png?v=06042016'>
+	<link rel='apple-touch-icon' sizes='76x76' href='/apple-touch-icon-76x76.png?v=06042016'>
+	<link rel='apple-touch-icon' sizes='114x114' href='/apple-touch-icon-114x114.png?v=06042016'>
+	<link rel='apple-touch-icon' sizes='120x120' href='/apple-touch-icon-120x120.png?v=06042016'>
+	<link rel='apple-touch-icon' sizes='144x144' href='/apple-touch-icon-144x144.png?v=06042016'>
+	<link rel='apple-touch-icon' sizes='152x152' href='/apple-touch-icon-152x152.png?v=06042016'>
+	<link rel='apple-touch-icon' sizes='180x180' href='/apple-touch-icon-180x180.png?v=06042016'>
+	<link rel='icon' type='image/png' href='/favicon-32x32.png?v=06042016' sizes='32x32'>
+	<link rel='icon' type='image/png' href='/favicon-194x194.png?v=06042016' sizes='194x194'>
+	<link rel='icon' type='image/png' href='/favicon-96x96.png?v=06042016' sizes='96x96'>
+	<link rel='icon' type='image/png' href='/android-chrome-192x192.png?v=06042016' sizes='192x192'>
+	<link rel='icon' type='image/png' href='/favicon-16x16.png?v=06042016' sizes='16x16'>
+	<link rel='manifest' href='/manifest.json?v=06042016'>
+	<link rel='mask-icon' href='/safari-pinned-tab.svg?v=06042016' color='#03a9f4'>
+	<link rel='shortcut icon' href='/favicon.ico?v=06042016'>
+	<meta name='msapplication-TileColor' content='#03a9f4'>
+	<meta name='msapplication-TileImage' content='/mstile-144x144.png?v=06042016'>
+	<meta name='theme-color' content='#03a9f4'>
+	<meta name='google' content='nositelinkssearchbox' />
+
+
+	<link href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.3/css/font-awesome.min.css' rel='stylesheet' type='text/css'>
+	<link href='https://fonts.googleapis.com/css?family=Roboto:100,400' rel='stylesheet'>
+	<link href='https://fonts.googleapis.com/icon?family=Material+Icons' rel='stylesheet'>
+	<link rel='stylesheet' href='/index.css'>
+	<script src='https://www.youtube.com/iframe_api'></script>
+	<script src='/vendor/jquery.min.js'></script>
+	<script type='text/javascript' src='https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.15.0/moment.min.js'></script>
+	<script type='text/javascript' src='https://cdnjs.cloudflare.com/ajax/libs/socket.io/1.4.8/socket.io.min.js'></script>
+	<script type='text/javascript' src='https://cdn.rawgit.com/atjonathan/lofig/master/dist/lofig.min.js'></script>
 </head>
 <body>
-	<script src="/bundle.js"></script>
+	<script src='/bundle.js'></script>
 	<script src='https://www.google.com/recaptcha/api.js'></script>
 </body>
 </html>

+ 41 - 0
frontend/build/manifest.json

@@ -0,0 +1,41 @@
+{
+	"name": "Musare",
+	"icons": [
+		{
+			"src": "\/android-chrome-36x36.png?v=06042016",
+			"sizes": "36x36",
+			"type": "image\/png",
+			"density": 0.75
+		},
+		{
+			"src": "\/android-chrome-48x48.png?v=06042016",
+			"sizes": "48x48",
+			"type": "image\/png",
+			"density": 1
+		},
+		{
+			"src": "\/android-chrome-72x72.png?v=06042016",
+			"sizes": "72x72",
+			"type": "image\/png",
+			"density": 1.5
+		},
+		{
+			"src": "\/android-chrome-96x96.png?v=06042016",
+			"sizes": "96x96",
+			"type": "image\/png",
+			"density": 2
+		},
+		{
+			"src": "\/android-chrome-144x144.png?v=06042016",
+			"sizes": "144x144",
+			"type": "image\/png",
+			"density": 3
+		},
+		{
+			"src": "\/android-chrome-192x192.png?v=06042016",
+			"sizes": "192x192",
+			"type": "image\/png",
+			"density": 4
+		}
+	]
+}

BIN
frontend/build/mstile-144x144.png


BIN
frontend/build/mstile-150x150.png


BIN
frontend/build/mstile-310x150.png


BIN
frontend/build/mstile-310x310.png


BIN
frontend/build/mstile-70x70.png


+ 2 - 0
frontend/build/robots.txt

@@ -0,0 +1,2 @@
+User-agent: *
+Disallow: /admin/

+ 116 - 0
frontend/build/safari-pinned-tab.svg

@@ -0,0 +1,116 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
+ "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
+ width="2048.000000pt" height="2048.000000pt" viewBox="0 0 2048.000000 2048.000000"
+ preserveAspectRatio="xMidYMid meet">
+<metadata>
+Created by potrace 1.11, written by Peter Selinger 2001-2013
+</metadata>
+<g transform="translate(0.000000,2048.000000) scale(0.100000,-0.100000)"
+fill="#000000" stroke="none">
+<path d="M14570 15310 c-124 -16 -164 -22 -184 -26 -12 -3 -32 -7 -46 -10 -55
+-11 -110 -27 -223 -65 -477 -160 -941 -510 -1344 -1014 -220 -276 -418 -592
+-609 -975 -224 -448 -435 -1013 -575 -1540 -11 -41 -25 -79 -30 -85 -5 -5 -34
+-10 -64 -11 l-55 -2 5 42 c6 45 8 62 24 181 6 44 13 103 16 130 3 28 8 61 11
+75 2 14 7 48 10 75 2 28 7 62 9 76 3 15 7 47 10 70 6 53 15 115 20 149 2 14 7
+48 10 75 7 65 19 160 40 311 4 29 14 116 20 174 3 28 11 106 21 195 13 109 5
+524 -11 615 -2 14 -7 52 -10 85 -3 33 -8 69 -10 80 -2 11 -7 36 -10 55 -7 49
+-14 86 -27 150 -6 30 -12 60 -14 65 -33 158 -137 425 -221 565 -81 136 -221
+287 -338 366 -82 55 -250 124 -342 140 -15 3 -41 8 -58 13 -33 8 -108 14 -195
+14 -367 1 -745 -160 -1165 -496 -214 -172 -541 -522 -759 -812 -196 -261 -454
+-659 -598 -925 -19 -36 -49 -91 -67 -124 -46 -84 -235 -460 -267 -531 -15 -33
+-42 -94 -62 -135 -61 -132 -162 -369 -241 -570 -43 -107 -82 -204 -86 -215 -4
+-11 -9 -24 -11 -30 -4 -17 -64 -179 -74 -200 -42 -92 -244 -730 -329 -1045
+-34 -124 -68 -248 -76 -276 -8 -28 -15 -58 -15 -67 0 -9 -4 -18 -9 -21 -5 -4
+-5 10 0 32 13 49 40 180 53 252 3 19 8 46 11 60 3 14 7 37 10 53 3 15 12 59
+20 97 8 38 17 82 20 97 3 16 7 39 10 53 3 14 23 122 45 240 21 118 42 229 45
+245 10 50 63 358 70 399 3 21 7 48 10 60 6 27 14 71 20 116 3 19 8 51 11 70 4
+19 8 44 10 55 22 148 45 304 49 325 2 14 11 79 20 145 9 66 18 131 20 145 2
+14 7 48 10 75 3 28 7 59 9 71 2 11 7 53 11 93 3 40 8 82 10 93 3 11 7 61 10
+112 4 50 8 101 10 113 6 39 18 314 18 423 0 117 -13 431 -18 461 -2 11 -7 56
+-11 102 -3 45 -8 84 -9 87 -2 3 -6 33 -10 65 -27 248 -133 558 -248 728 -96
+141 -271 292 -403 347 -27 12 -56 25 -65 29 -27 15 -162 49 -244 61 -79 12
+-332 17 -390 7 -16 -3 -50 -8 -75 -12 -124 -18 -335 -84 -460 -143 -277 -131
+-564 -381 -802 -697 -50 -67 -157 -225 -187 -277 -10 -18 -40 -69 -66 -113
+-106 -182 -255 -508 -350 -771 -49 -132 -115 -333 -121 -364 -3 -14 -11 -32
+-19 -40 -31 -34 4 -244 63 -380 11 -25 34 -65 52 -89 l32 -44 60 7 c103 10
+188 60 225 130 24 45 146 200 258 325 102 115 326 329 445 427 19 16 37 31 40
+35 4 5 127 96 210 156 162 117 442 260 550 279 14 3 39 8 55 12 48 12 137 7
+171 -11 44 -23 81 -83 103 -167 16 -63 18 -102 15 -240 -2 -91 -6 -183 -8
+-205 -2 -22 -7 -78 -11 -125 -4 -47 -8 -105 -10 -130 -2 -25 -6 -88 -10 -140
+-4 -52 -9 -113 -11 -134 -2 -22 -6 -80 -9 -130 -3 -50 -7 -107 -10 -126 -2
+-19 -7 -74 -10 -122 -3 -48 -8 -107 -10 -130 -2 -24 -7 -79 -10 -123 -3 -44
+-8 -102 -10 -130 -3 -27 -7 -77 -10 -110 -5 -68 -23 -265 -30 -340 -3 -27 -7
+-77 -10 -110 -5 -57 -8 -90 -20 -207 -3 -29 -8 -69 -10 -88 -3 -19 -7 -66 -11
+-105 -3 -38 -7 -77 -9 -86 -2 -10 -7 -50 -10 -90 -4 -40 -8 -81 -10 -90 -2
+-10 -6 -44 -10 -76 -3 -32 -7 -69 -10 -83 -2 -14 -7 -47 -10 -75 -3 -27 -7
+-66 -10 -85 -2 -19 -7 -56 -10 -82 -3 -27 -7 -67 -10 -90 -3 -24 -7 -56 -9
+-73 -2 -16 -7 -46 -10 -65 -3 -19 -8 -51 -10 -70 -11 -82 -18 -137 -22 -160
+-2 -14 -7 -45 -10 -70 -3 -25 -7 -57 -10 -72 -2 -16 -6 -43 -9 -60 -3 -18 -7
+-44 -10 -58 -3 -14 -7 -38 -9 -55 -3 -16 -16 -104 -31 -195 -14 -91 -28 -176
+-30 -190 -2 -14 -11 -68 -20 -120 -9 -52 -18 -111 -21 -130 -37 -249 -118
+-689 -140 -767 l-15 -53 -62 0 c-133 -1 -307 -34 -441 -84 -324 -122 -603
+-357 -751 -631 -171 -316 -163 -730 20 -1097 51 -100 69 -128 140 -213 160
+-193 343 -299 630 -366 72 -16 131 -21 295 -24 113 -2 230 0 260 5 30 4 86 11
+124 15 88 8 232 45 322 80 201 80 383 223 527 412 49 64 151 231 179 293 9 19
+28 62 43 95 59 131 175 513 209 688 10 52 119 479 171 672 9 33 27 98 40 145
+149 558 448 1485 685 2125 10 28 35 95 55 150 20 55 41 109 45 120 5 11 28 70
+51 130 89 235 257 649 305 752 13 28 24 54 24 57 0 3 13 35 30 71 16 36 30 67
+30 70 0 2 16 38 35 79 19 42 35 78 35 80 0 2 15 37 34 78 19 40 57 123 84 183
+50 110 225 477 240 505 5 8 42 78 82 155 226 433 400 718 596 980 232 310 410
+453 572 460 312 13 503 -219 567 -688 4 -27 8 -56 10 -65 3 -18 16 -211 21
+-327 6 -117 6 -776 0 -835 -3 -30 -8 -111 -11 -180 -4 -69 -8 -141 -10 -160
+-2 -19 -6 -73 -10 -120 -3 -47 -8 -96 -10 -110 -2 -14 -6 -54 -10 -90 -6 -80
+-13 -141 -19 -189 -3 -20 -10 -81 -16 -136 -6 -55 -13 -111 -16 -125 -2 -14
+-7 -47 -10 -75 -3 -27 -7 -61 -9 -75 -3 -14 -7 -50 -10 -80 -4 -30 -8 -62 -11
+-70 -2 -8 -6 -35 -9 -60 -3 -25 -7 -56 -10 -70 -2 -14 -7 -45 -10 -70 -3 -25
+-8 -52 -10 -60 -2 -8 -7 -40 -10 -70 -3 -30 -8 -62 -10 -70 -3 -8 -7 -31 -9
+-50 -5 -35 -31 -194 -40 -240 -3 -14 -12 -68 -21 -120 -9 -52 -18 -106 -21
+-120 -7 -43 -43 -243 -49 -275 -2 -16 -7 -41 -10 -55 -3 -14 -8 -41 -11 -60
+-9 -49 -10 -55 -27 -140 -9 -41 -19 -93 -23 -115 -4 -22 -8 -44 -9 -50 -5 -19
+-26 -130 -32 -160 -6 -37 -74 -376 -109 -540 -14 -66 -36 -167 -50 -225 -14
+-58 -27 -114 -30 -124 -18 -92 -66 -181 -86 -161 -11 11 -132 19 -263 18 -428
+-3 -773 -158 -978 -438 -109 -149 -179 -338 -205 -554 -9 -75 -8 -350 1 -431
+15 -133 17 -149 32 -218 103 -490 697 -851 1380 -837 233 5 368 36 560 131
+406 201 716 597 899 1149 51 155 52 159 80 270 24 97 30 124 41 183 3 15 8 38
+11 52 8 33 15 70 51 260 25 135 68 359 89 460 14 69 27 136 30 150 2 14 7 36
+10 50 3 14 8 36 10 50 13 69 98 479 121 580 14 63 30 135 34 160 17 84 80 365
+110 490 30 122 64 270 75 320 3 14 14 61 25 105 12 44 22 87 24 95 2 8 11 44
+20 80 9 36 19 73 20 82 7 32 116 442 141 528 33 112 85 291 90 310 24 97 203
+644 279 856 71 196 220 577 270 689 15 33 39 87 53 120 119 267 331 633 479
+829 313 411 618 612 944 621 149 4 249 -30 330 -115 65 -69 122 -183 144 -289
+2 -9 8 -38 15 -65 6 -27 13 -70 16 -95 3 -25 7 -56 9 -69 24 -141 24 -665 0
+-1047 -3 -59 -16 -195 -20 -214 -1 -9 -6 -50 -10 -91 -4 -41 -9 -83 -11 -94
+-2 -10 -6 -44 -10 -75 -3 -31 -8 -63 -9 -71 -2 -9 -7 -43 -11 -75 -4 -33 -9
+-62 -9 -65 -1 -3 -5 -32 -9 -65 -5 -33 -10 -67 -11 -75 -2 -8 -6 -40 -11 -70
+-4 -30 -8 -57 -9 -60 -1 -3 -5 -27 -10 -55 -4 -27 -9 -59 -11 -70 -3 -11 -11
+-60 -19 -110 -8 -49 -17 -101 -20 -115 -3 -14 -8 -38 -11 -55 -3 -16 -16 -91
+-30 -165 -14 -74 -28 -148 -31 -165 -4 -25 -22 -113 -39 -190 -2 -11 -6 -32
+-9 -48 -3 -15 -12 -60 -21 -100 -8 -39 -17 -82 -20 -94 -2 -13 -6 -33 -8 -45
+-3 -13 -12 -57 -21 -98 -9 -41 -19 -86 -21 -100 -3 -14 -14 -63 -24 -110 -18
+-81 -23 -103 -36 -167 -3 -16 -11 -56 -19 -91 -8 -34 -17 -77 -20 -95 -4 -17
+-15 -70 -25 -117 -10 -47 -21 -96 -23 -110 -3 -14 -13 -61 -22 -105 -10 -44
+-19 -93 -21 -110 -2 -16 -7 -37 -10 -47 -2 -9 -7 -25 -9 -35 -7 -32 -47 -237
+-51 -263 -6 -34 -32 -170 -50 -257 -8 -39 -16 -89 -20 -110 -3 -21 -12 -74
+-19 -118 -8 -44 -17 -96 -20 -115 -3 -19 -7 -46 -10 -60 -2 -14 -7 -43 -10
+-65 -3 -23 -8 -52 -10 -65 -3 -14 -7 -43 -9 -65 -9 -83 -17 -151 -21 -170 -2
+-11 -7 -54 -11 -95 -3 -41 -8 -91 -9 -110 -21 -214 -26 -484 -11 -610 13 -114
+47 -272 77 -365 40 -119 145 -331 211 -426 115 -164 271 -326 390 -402 159
+-103 419 -215 563 -243 22 -5 42 -9 45 -9 3 -1 16 -5 30 -8 107 -27 369 -40
+510 -24 198 22 441 113 570 212 311 242 601 799 534 1025 -12 41 -12 43 3 25
+14 -18 15 -13 9 70 -11 167 -32 276 -83 425 -64 192 -135 315 -242 420 -210
+208 -521 310 -948 314 l-132 1 5 35 c5 32 15 96 30 175 19 110 76 396 84 425
+5 19 12 50 15 68 3 18 28 133 55 255 28 122 53 233 56 247 3 14 11 53 19 88 8
+34 17 74 20 90 2 15 9 42 14 60 5 17 8 32 6 32 -3 0 0 11 5 25 5 14 12 36 14
+48 4 23 64 291 71 317 8 34 46 207 51 235 3 17 12 55 19 85 8 30 26 111 40
+180 26 120 30 143 39 193 2 12 15 76 29 142 14 66 28 134 30 150 3 17 10 53
+16 80 6 28 12 64 15 80 2 17 11 68 20 115 20 107 23 124 41 240 9 52 18 106
+20 120 10 59 14 89 24 160 6 41 13 95 16 120 3 25 10 74 15 110 15 102 18 130
+25 205 4 39 8 77 10 85 2 8 7 58 10 110 4 52 8 111 11 130 24 228 24 849 -1
+1085 -19 176 -31 270 -49 362 -3 11 -8 43 -11 70 -4 26 -11 66 -17 88 -6 22
+-11 47 -12 55 -2 15 -6 32 -32 135 -30 117 -97 322 -141 430 -48 119 -142 308
+-155 313 -4 2 -8 8 -8 13 0 6 -19 38 -42 72 -179 266 -406 463 -628 542 -48
+18 -51 19 -120 36 -25 6 -54 13 -65 16 -33 8 -326 10 -385 3z"/>
+</g>
+</svg>

+ 5 - 3
frontend/components/Admin/QueueSongs.vue

@@ -26,9 +26,9 @@
 						<td>{{ song.genres.join(', ') }}</td>
 						<td>{{ song.requestedBy }}</td>
 						<td>
-							<a class='button is-primary' @click='edit(song, index)'>Edit</a>
-							<a class='button is-success' @click='add(song)'>Add</a>
-							<a class='button is-danger' @click='remove(song._id, index)'>Remove</a>
+							<a class='button is-primary' href='#' @click='edit(song, index)'>Edit</a>
+							<a class='button is-success' href='#' @click='add(song)'>Add</a>
+							<a class='button is-danger' href='#' @click='remove(song._id, index)'>Remove</a>
 						</td>
 					</tr>
 				</tbody>
@@ -198,4 +198,6 @@
 	}
 
 	td { vertical-align: middle; }
+
+	.is-primary:focus { background-color: #029ce3 !important; }
 </style>

+ 2 - 2
frontend/components/Admin/Reports.vue

@@ -26,8 +26,8 @@
 							<span>{{ report.description }}</span>
 						</td>
 						<td>
-							<a class='button is-warning' @click='toggleModal(report)'>Issues</a>
-							<a class='button is-primary' @click='resolve(report._id)'>Resolve</a>
+							<a class='button is-warning' href='#' @click='toggleModal(report)'>Issues</a>
+							<a class='button is-primary' href='#' @click='resolve(report._id)'>Resolve</a>
 						</td>
 					</tr>
 				</tbody>

+ 4 - 2
frontend/components/Admin/Songs.vue

@@ -26,8 +26,8 @@
 						<td>{{ song.genres.join(', ') }}</td>
 						<td>{{ song.requestedBy }}</td>
 						<td>
-							<a class='button is-primary' @click='edit(song, index)'>Edit</a>
-							<a class='button is-danger' @click='remove(song._id, index)'>Remove</a>
+							<a class='button is-primary' href='#' @click='edit(song, index)'>Edit</a>
+							<a class='button is-danger' href='#' @click='remove(song._id, index)'>Remove</a>
 						</td>
 					</tr>
 				</tbody>
@@ -195,4 +195,6 @@
 	}
 
 	td { vertical-align: middle; }
+
+	.is-primary:focus { background-color: #029ce3 !important; }
 </style>

+ 6 - 4
frontend/components/Admin/Stations.vue

@@ -26,7 +26,7 @@
 							<span>{{ station.description }}</span>
 						</td>
 						<td>
-							<a class='button is-danger' @click='removeStation(index)'>Remove</a>
+							<a class='button is-danger' @click='removeStation(index)' href='#'>Remove</a>
 						</td>
 					</tr>
 				</tbody>
@@ -58,7 +58,7 @@
 						<label class='label'>Genres</label>
 						<p class='control has-addons'>
 							<input class='input' id='new-genre' type='text' placeholder='Genre'>
-							<a class='button is-info' @click='addGenre()'>Add genre</a>
+							<a class='button is-info' href='#' @click='addGenre()'>Add genre</a>
 						</p>
 						<span class='tag is-info' v-for='(index, genre) in newStation.genres' track-by='$index'>
 							{{ genre }}
@@ -67,7 +67,7 @@
 						<label class='label'>Blacklisted Genres</label>
 						<p class='control has-addons'>
 							<input class='input' id='new-blacklisted-genre' type='text' placeholder='Blacklisted Genre'>
-							<a class='button is-info' @click='addBlacklistedGenre()'>Add blacklisted genre</a>
+							<a class='button is-info' href='#' @click='addBlacklistedGenre()'>Add blacklisted genre</a>
 						</p>
 						<span class='tag is-info' v-for='(index, genre) in newStation.blacklistedGenres' track-by='$index'>
 							{{ genre }}
@@ -76,7 +76,7 @@
 					</div>
 				</div>
 				<footer class='card-footer'>
-					<a class='card-footer-item' @click='createStation()'>Create</a>
+					<a class='card-footer-item' @click='createStation()' href='#'>Create</a>
 				</footer>
 			</div>
 		</div>
@@ -175,4 +175,6 @@
 		max-width: 10vw;
 		vertical-align: middle;
 	}
+
+	.is-info:focus { background-color: #0398db; }
 </style>

+ 2 - 2
frontend/components/MainFooter.vue

@@ -10,10 +10,10 @@
 						<img src='/assets/social/github.svg'/>
 					</a>
 					<a class='icon' href='https://twitter.com/MusareApp' title='Twitter Account'>
-						<i class='fa fa-twitter'></i>
+						<img src='/assets/social/twitter.svg'/>
 					</a>
 					<a class='icon' href='https://www.facebook.com/MusareMusic/' title='Facebook Page'>
-						<i class='fa fa-facebook'></i>
+						<img src='/assets/social/facebook.svg'/>
 					</a>
 					<a class='icon' href='https://discord.gg/Y5NxYGP' title='Discord Server'>
 						<img src='/assets/social/discord.svg'/>

+ 2 - 2
frontend/components/MainHeader.vue

@@ -16,9 +16,9 @@
 			<a class="nav-item is-tab admin" href="#" v-link="{ path: '/admin' }" v-if="$parent.$parent.role === 'admin'">
 				<strong>Admin</strong>
 			</a>
-			<a class="nav-item is-tab" href="#">
+			<!--a class="nav-item is-tab" href="#">
 				About
-			</a>
+			</a-->
 			<a class="nav-item is-tab" href="#" v-link="{ path: '/news' }">
 				News
 			</a>

+ 20 - 17
frontend/components/Modals/AddSongToQueue.vue

@@ -7,10 +7,10 @@
 				<button class="delete" @click="$parent.toggleModal('addSongToQueue')" ></button>
 			</header>
 			<section class="modal-card-body">
-				<aside class='menu' v-if='$parent.$parent.loggedIn'>
+				<aside class='menu' v-if='$parent.$parent.loggedIn && $parent.type === "community"'>
 					<ul class='menu-list'>
 						<li v-for='playlist in playlists' track-by='$index'>
-							<a :href='' target='_blank' @click='$parent.editPlaylist(playlist._id)'>{{ playlist.displayName }}</a>
+							<a href='#' target='_blank' @click='$parent.editPlaylist(playlist._id)'>{{ playlist.displayName }}</a>
 							<div class='controls'>
 								<a href='#' @click='selectPlaylist(playlist._id)' v-if="!isPlaylistSelected(playlist._id)"><i class='material-icons'>panorama_fish_eye</i></a>
 								<a href='#' @click='unSelectPlaylist()' v-if="isPlaylistSelected(playlist._id)"><i class='material-icons'>lens</i></a>
@@ -21,10 +21,10 @@
 				</aside>
 				<div class="control is-grouped">
 					<p class="control is-expanded">
-						<input class="input" type="text" placeholder="YouTube Query" v-model="querySearch">
+						<input class="input" type="text" placeholder="YouTube Query" v-model='querySearch' autofocus @keyup.enter='submitQuery()'>
 					</p>
 					<p class="control">
-						<a class="button is-info" @click="submitQuery()">
+						<a class="button is-info" @click="submitQuery()" href='#'>
 							Search
 						</a>
 					</p>
@@ -37,7 +37,7 @@
 							</td>
 							<td>{{ result.title }}</td>
 							<td>
-								<a class="button is-success" @click="addSongToQueue(result.id)">
+								<a class="button is-success" @click="addSongToQueue(result.id)" href='#'>
 									Add
 								</a>
 							</td>
@@ -52,6 +52,7 @@
 <script>
 	import { Toast } from 'vue-roaster';
 	import io from '../../io';
+	import auth from '../../auth';
 
 	export default {
 		data() {
@@ -64,7 +65,6 @@
 		},
 		methods: {
 			isPlaylistSelected: function(playlistId) {
-				console.log(this.privatePlaylistQueueSelected === playlistId);
 				return this.privatePlaylistQueueSelected === playlistId;
 			},
 			selectPlaylist: function (playlistId) {
@@ -72,6 +72,7 @@
 				if (_this.$parent.type === 'community') {
 					_this.privatePlaylistQueueSelected = playlistId;
 					_this.$parent.privatePlaylistQueueSelected = playlistId;
+					_this.$parent.addFirstPrivatePlaylistSongToQueue();
 				}
 			},
 			unSelectPlaylist: function () {
@@ -85,19 +86,13 @@
 				let _this = this;
 				if (_this.$parent.type === 'community') {
 					_this.socket.emit('stations.addToQueue', _this.$parent.stationId, songId, data => {
-						if (data.status !== 'success') {
-							Toast.methods.addToast(`Error: ${data.message}`, 8000);
-						} else {
-							Toast.methods.addToast(`${data.message}`, 4000);
-						}
+						if (data.status !== 'success') Toast.methods.addToast(`Error: ${data.message}`, 8000);
+						else Toast.methods.addToast(`${data.message}`, 4000);
 					});
 				} else {
 					_this.socket.emit('queueSongs.add', songId, data => {
-						if (data.status !== 'success') {
-							Toast.methods.addToast(`Error: ${data.message}`, 8000);
-						} else {
-							Toast.methods.addToast(`${data.message}`, 4000);
-						}
+						if (data.status !== 'success') Toast.methods.addToast(`Error: ${data.message}`, 8000);
+						else Toast.methods.addToast(`${data.message}`, 4000);
 					});
 				}
 			},
@@ -144,4 +139,12 @@
 			}
 		}
 	}
-</script>
+</script>
+
+<style type='scss' scoped>
+	tr td {
+		vertical-align: middle;
+
+		img { width: 55px; }
+	}
+</style>

+ 2 - 2
frontend/components/Modals/CreateCommunityStation.vue

@@ -10,7 +10,7 @@
 				<!-- validation to check if exists http://bulma.io/documentation/elements/form/ -->
 				<label class='label'>Unique ID (lowercase, a-z, used in the url)</label>
 				<p class='control'>
-					<input class='input' type='text' placeholder='Name...' v-model='newCommunity._id'>
+					<input class='input' type='text' placeholder='Name...' v-model='newCommunity._id' autofocus>
 				</p>
 				<label class='label'>Display Name</label>
 				<p class='control'>
@@ -18,7 +18,7 @@
 				</p>
 				<label class='label'>Description</label>
 				<p class='control'>
-					<input class='input' type='text' placeholder='Description...' v-model='newCommunity.description'>
+					<input class='input' type='text' placeholder='Description...' v-model='newCommunity.description' @keyup.enter="submitModal()">
 				</p>
 			</section>
 			<footer class='modal-card-foot'>

+ 38 - 23
frontend/components/Modals/EditSong.vue

@@ -14,14 +14,16 @@
 							</p>
 						</form>
 						<p class='control has-addons'>
-							<a class='button'>
-								<i class='material-icons' @click='$parent.video.settings("pause")' v-if='!$parent.video.paused'>pause</i>
-								<i class='material-icons' @click='$parent.video.settings("play")' v-else>play_arrow</i>
+							<a class='button' @click='$parent.video.settings("pause")' v-if='!$parent.video.paused' href='#'>
+								<i class='material-icons'>pause</i>
 							</a>
-							<a class='button' @click='$parent.video.settings("stop")'>
+							<a class='button' @click='$parent.video.settings("play")' v-if='$parent.video.paused' href='#'>
+								<i class='material-icons'>play_arrow</i>
+							</a>
+							<a class='button' @click='$parent.video.settings("stop")' href='#'>
 								<i class='material-icons'>stop</i>
 							</a>
-							<a class='button' @click='$parent.video.settings("skipToLast10Secs")'>
+							<a class='button' @click='$parent.video.settings("skipToLast10Secs")' href='#'>
 								<i class='material-icons'>fast_forward</i>
 							</a>
 						</p>
@@ -30,10 +32,14 @@
 				<h5 class='has-text-centered'>Thumbnail Preview</h5>
 				<img class='thumbnail-preview' :src='$parent.editing.song.thumbnail' onerror="this.src='/assets/notes-transparent.png'">
 
-				<label class='label'>Thumbnail URL</label>
-				<p class='control'>
-					<input class='input' type='text' v-model='$parent.editing.song.thumbnail'>
-				</p>
+				<div class="control is-horizontal">
+					<div class="control-label">
+						<label class="label">Thumbnail URL</label>
+					</div>
+					<div class="control">
+						<input class='input' type='text' v-model='$parent.editing.song.thumbnail'>
+					</div>
+				</div>
 
 				<h5 class='has-text-centered'>Edit Info</h5>
 
@@ -43,20 +49,24 @@
 						Explicit
 					</label>
 				</p>
-				<label class='label'>Song ID</label>
-				<p class='control'>
-					<input class='input' type='text' v-model='$parent.editing.song._id'>
-				</p>
-				<label class='label'>Song Title</label>
-				<p class='control'>
-					<input class='input' type='text' v-model='$parent.editing.song.title'>
-				</p>
+				<label class='label'>Song ID & Title</label>
+				<div class="control is-horizontal">
+					<div class="control is-grouped">
+						<p class='control is-expanded'>
+							<input class='input' type='text' v-model='$parent.editing.song._id'>
+						</p>
+						<p class='control is-expanded'>
+							<input class='input' type='text' v-model='$parent.editing.song.title' autofocus>
+						</p>
+					</div>
+				</div>
+				<label class='label'>Artists & Genres</label>
 				<div class='control is-horizontal'>
-					<div class='control is-grouped'>
+					<div class='control is-grouped artist-genres'>
 						<div>
 							<p class='control has-addons'>
 								<input class='input' id='new-artist' type='text' placeholder='Artist'>
-								<a class='button is-info' @click='$parent.addTag("artists")'>Add Artist</a>
+								<a class='button is-info' @click='$parent.addTag("artists")' href='#'>Add Artist</a>
 							</p>
 							<span class='tag is-info' v-for='(index, artist) in $parent.editing.song.artists' track-by='$index'>
 								{{ artist }}
@@ -66,7 +76,7 @@
 						<div>
 							<p class='control has-addons'>
 								<input class='input' id='new-genre' type='text' placeholder='Genre'>
-								<a class='button is-info' @click='$parent.addTag("genres")'>Add Genre</a>
+								<a class='button is-info' @click='$parent.addTag("genres")' href='#'>Add Genre</a>
 							</p>
 							<span class='tag is-info' v-for='(index, genre) in $parent.editing.song.genres' track-by='$index'>
 								{{ genre }}
@@ -86,11 +96,11 @@
 
 			</section>
 			<footer class='modal-card-foot'>
-				<a class='button is-success' @click='$parent.save($parent.editing.song)'>
+				<a class='button is-success' @click='$parent.save($parent.editing.song)' href='#'>
 					<i class='material-icons save-changes'>done</i>
 					<span>&nbsp;Save</span>
 				</a>
-				<a class='button is-danger' @click='$parent.toggleModal()'>
+				<a class='button is-danger' @click='$parent.toggleModal()' href='#'>
 					<span>&nbsp;Cancel</span>
 				</a>
 			</footer>
@@ -213,13 +223,18 @@
 		align-items: center;
 	}
 
+	.artist-genres {
+		display: flex;
+    	justify-content: space-between;
+	}
+
 	#volumeSlider { margin-bottom: 15px; }
 
 	.has-text-centered { padding: 10px; }
 
 	.thumbnail-preview {
 		display: flex;
-		margin: 0 auto;
+		margin: 0 auto 25px auto;
 		max-width: 200px;
 		width: 100%;
 	}

+ 29 - 12
frontend/components/Modals/EditStation.vue

@@ -10,10 +10,10 @@
 				<label class='label'>Display name</label>
 				<div class='control is-grouped'>
 					<p class='control is-expanded'>
-						<input class='input' type='text' placeholder='Station Display Name' v-model='data.displayName'>
+						<input class='input' type='text' placeholder='Station Display Name' v-model='data.displayName' autofocus>
 					</p>
 					<p class='control'>
-						<a class='button is-info' @click='updateDisplayName()'>Update</a>
+						<a class='button is-info' @click='updateDisplayName()' href='#'>Update</a>
 					</p>
 				</div>
 				<label class='label'>Description</label>
@@ -22,7 +22,7 @@
 						<input class='input' type='text' placeholder='Station Display Name' v-model='data.description'>
 					</p>
 					<p class='control'>
-						<a class='button is-info' @click='updateDescription()'>Update</a>
+						<a class='button is-info' @click='updateDescription()' href='#'>Update</a>
 					</p>
 				</div>
 				<label class='label'>Privacy</label>
@@ -37,18 +37,18 @@
 						</span>
 					</p>
 					<p class='control'>
-						<a class='button is-info' @click='updatePrivacy()'>Update</a>
+						<a class='button is-info' @click='updatePrivacy()' href='#'>Update</a>
 					</p>
 				</div>
 				<div class='control is-grouped' v-if="$parent.type === 'community'">
-					<p class="control is-expanded">
-						<label class="checkbox">
+					<p class="control is-expanded party-mode-outer">
+						<label class="checkbox party-mode-inner">
 							<input type="checkbox" v-model="data.partyMode">
-							Party mode
+							&nbsp;Party mode
 						</label>
 					</p>
 					<p class='control'>
-						<a class='button is-info' @click='updatePartyMode()'>Update</a>
+						<a class='button is-info' @click='updatePartyMode()' href='#'>Update</a>
 					</p>
 				</div>
 			</section>
@@ -74,25 +74,37 @@
 		methods: {
 			updateDisplayName: function () {
 				this.socket.emit('stations.updateDisplayName', this.data.stationId, this.data.displayName, res => {
-					if (res.status == 'success') return Toast.methods.addToast(res.message, 4000);
+					if (res.status === 'success') {
+						this.$parent.station.displayName = this.data.displayName;
+						return Toast.methods.addToast(res.message, 4000);
+					}
 					Toast.methods.addToast(res.message, 8000);
 				});
 			},
 			updateDescription: function () {
 				this.socket.emit('stations.updateDescription', this.data.stationId, this.data.description, res => {
-					if (res.status == 'success') return Toast.methods.addToast(res.message, 4000);
+					if (res.status === 'success') {
+						this.$parent.station.description = this.data.description;
+						return Toast.methods.addToast(res.message, 4000);
+					}
 					Toast.methods.addToast(res.message, 8000);
 				});
 			},
 			updatePrivacy: function () {
 				this.socket.emit('stations.updatePrivacy', this.data.stationId, this.data.privacy, res => {
-					if (res.status == 'success') return Toast.methods.addToast(res.message, 4000);
+					if (res.status === 'success') {
+						this.$parent.station.privacy = this.data.privacy;
+						return Toast.methods.addToast(res.message, 4000);
+					}
 					Toast.methods.addToast(res.message, 8000);
 				});
 			},
 			updatePartyMode: function () {
 				this.socket.emit('stations.updatePartyMode', this.data.stationId, this.data.partyMode, res => {
-					if (res.status == 'success') return Toast.methods.addToast(res.message, 4000);
+					if (res.status === 'success') {
+					this.$parent.station.partyMode = this.data.partyMode;
+						return Toast.methods.addToast(res.message, 4000);
+					}
 					Toast.methods.addToast(res.message, 8000);
 				});
 			}
@@ -129,4 +141,9 @@
 	.table { margin-bottom: 0; }
 
 	h5 { padding: 20px 0; }
+
+	.party-mode-inner, .party-mode-outer {
+		display: flex;
+		align-items: center;
+	}
 </style>

+ 2 - 2
frontend/components/Modals/IssuesModal.vue

@@ -29,10 +29,10 @@
 
 			</section>
 			<footer class='modal-card-foot'>
-				<a class='button is-primary' @click='$parent.resolve($parent.editing._id)'>
+				<a class='button is-primary' @click='$parent.resolve($parent.editing._id)' href='#'>
 					<span>Resolve</span>
 				</a>
-				<a class='button is-danger' @click='$parent.toggleModal()'>
+				<a class='button is-danger' @click='$parent.toggleModal()' href='#'>
 					<span>Cancel</span>
 				</a>
 			</footer>

+ 6 - 3
frontend/components/Modals/Login.vue

@@ -16,10 +16,10 @@
 				<p class='control'>
 					<input class='input' type='password' placeholder='Password...' v-model='$parent.login.password' v-on:keypress='$parent.submitOnEnter(submitModal, $event)'>
 				</p>
-				<p>By logging in you agree to our <a href="/terms" v-link="{ path: '/terms' }">Terms of Service</a> and <a href="/privacy" v-link="{ path: '/privacy' }">Privacy Policy</a>.</p>
+				<p>By logging in/registering you agree to our <a href="/terms" v-link="{ path: '/terms' }">Terms of Service</a> and <a href="/privacy" v-link="{ path: '/privacy' }">Privacy Policy</a>.</p>
 			</section>
 			<footer class='modal-card-foot'>
-				<a class='button is-primary' @click='submitModal("login")'>Submit</a>
+				<a class='button is-primary' href='#' @click='submitModal("login")'>Submit</a>
 				<a class='button is-github' :href='$parent.serverDomain + "/auth/github/authorize"'>
 					<div class='icon'>
 						<img class='invert' src='/assets/social/github.svg'/>
@@ -52,9 +52,12 @@
 
 <style type='scss' scoped>
 	.button.is-github {
-		background-color: #333 !important;
+		background-color: #333;
 		color: #fff !important;
 	}
 
+	.is-github:focus { background-color: #1a1a1a; }
+	.is-primary:focus { background-color: #029ce3 !important; }
+
 	.invert { filter: brightness(5); }
 </style>

+ 1 - 1
frontend/components/Modals/Playlists/Create.vue

@@ -8,7 +8,7 @@
 			</header>
 			<section class='modal-card-body'>
 				<p class='control is-expanded'>
-					<input class='input' type='text' placeholder='Playlist Display Name' v-model='playlist.displayName'>
+					<input class='input' type='text' placeholder='Playlist Display Name' v-model='playlist.displayName' autofocus @keyup.enter='createPlaylist()'>
 				</p>
 			</section>
 			<footer class='modal-card-foot'>

+ 21 - 22
frontend/components/Modals/Playlists/Edit.vue

@@ -12,14 +12,14 @@
 						<li v-for='song in playlist.songs' track-by='$index'>
 							<a :href='' target='_blank'>{{ song.title }}</a>
 							<div class='controls'>
-								<!--a href='#'>
-									<i class='material-icons' v-if='playlist.songs[0] !== song' @click='promoteSong($index)'>keyboard_arrow_up</i>
+								<a href='#' @click='promoteSong(song._id)'>
+									<i class='material-icons' v-if='$index > 0' @click='promoteSong(song._id)'>keyboard_arrow_up</i>
 									<i class='material-icons' style='opacity: 0' v-else>error</i>
 								</a>
-								<a href='#'>
-									<i class='material-icons' v-if='playlist.songs.length - 1 !== $index' @click='demoteSong($index)'>keyboard_arrow_down</i>
+								<a href='#' @click='demoteSong(song._id)'>
+									<i class='material-icons' v-if='playlist.songs.length - 1 !== $index' @click='demoteSong(song._id)'>keyboard_arrow_down</i>
 									<i class='material-icons' style='opacity: 0' v-else>error</i>
-								</a-->
+								</a>
 								<a href='#' @click='removeSongFromPlaylist(song._id)'><i class='material-icons'>delete</i></a>
 							</div>
 						</li>
@@ -28,10 +28,10 @@
 				</aside>
 				<div class='control is-grouped'>
 					<p class='control is-expanded'>
-						<input class='input' type='text' placeholder='Search for Song to add' v-model='songQuery'>
+						<input class='input' type='text' placeholder='Search for Song to add' v-model='songQuery' autofocus @keyup.enter='searchForSongs()'>
 					</p>
 					<p class='control'>
-						<a class='button is-info' @click='searchForSongs()'>Search</a>
+						<a class='button is-info' @click='searchForSongs()' href="#">Search</a>
 					</p>
 				</div>
 				<table class='table' v-if='songQueryResults.length > 0'>
@@ -42,7 +42,7 @@
 							</td>
 							<td>{{ result.title }}</td>
 							<td>
-								<a class='button is-success' @click='addSongToPlaylist(result.id)'>
+								<a class='button is-success' @click='addSongToPlaylist(result.id)' href='#'>
 									Add
 								</a>
 							</td>
@@ -51,24 +51,24 @@
 				</table>
 				<div class='control is-grouped'>
 					<p class='control is-expanded'>
-						<input class='input' type='text' placeholder='YouTube Playlist URL' v-model='importQuery'>
+						<input class='input' type='text' placeholder='YouTube Playlist URL' v-model='importQuery' @keyup.enter="importPlaylist()">
 					</p>
 					<p class='control'>
-						<a class='button is-info' @click='importPlaylist()'>Import</a>
+						<a class='button is-info' @click='importPlaylist()' href="#">Import</a>
 					</p>
 				</div>
 				<h5>Edit playlist details:</h5>
 				<div class='control is-grouped'>
 					<p class='control is-expanded'>
-						<input class='input' type='text' placeholder='Playlist Display Name' v-model='playlist.displayName'>
+						<input class='input' type='text' placeholder='Playlist Display Name' v-model='playlist.displayName' @keyup.enter="renamePlaylist()">
 					</p>
 					<p class='control'>
-						<a class='button is-info' @click='renamePlaylist()'>Rename</a>
+						<a class='button is-info' @click='renamePlaylist()' href="#">Rename</a>
 					</p>
 				</div>
 			</section>
 			<footer class='modal-card-foot'>
-				<a class='button is-danger' @click='removePlaylist()'>Remove Playlist</a>
+				<a class='button is-danger' @click='removePlaylist()' href="#">Remove Playlist</a>
 			</footer>
 		</div>
 	</div>
@@ -112,7 +112,6 @@
 								thumbnail: res.data.items[i].snippet.thumbnails.default.url
 							});
 						}
-						Toast.methods.addToast(res.message, 3000);
 					} else if (res.status === 'error') Toast.methods.addToast(res.message, 3000);
 				});
 			},
@@ -149,19 +148,19 @@
 						_this.$parent.toggleModal('editPlaylist');
 					}
 				});
-			},/*
-			promoteSong: function (fromIndex) {
+			},
+			promoteSong: function (songId) {
 				let _this = this;
-				_this.socket.emit('playlists.promoteSong', _this.playlist._id, fromIndex, res => {
-					if (res.status === 'success') _this.$set('playlist.songs', res.data); // bug: v-for is not updating
+				_this.socket.emit('playlists.moveSongToTop', _this.playlist._id, songId, res => {
+					Toast.methods.toast(4000, res.message);
 				});
 			},
-			demoteSong: function (fromIndex) {
+			demoteSong: function (songId) {
 				let _this = this;
-				_this.socket.emit('playlists.demoteSong', _this.playlist._id, fromIndex, res => {
-					if (res.status === 'success') _this.$set('playlist.songs', res.data); // bug: v-for is not updating
+				_this.socket.emit('playlists.moveSongToBottom', _this.playlist._id, songId, res => {
+					Toast.methods.toast(4000, res.message);
 				});
-			}*/
+			}
 		},
 		ready: function () {
 			let _this = this;

+ 9 - 4
frontend/components/Modals/Register.vue

@@ -10,7 +10,7 @@
 				<!-- validation to check if exists http://bulma.io/documentation/elements/form/ -->
 				<label class='label'>Email</label>
 				<p class='control'>
-					<input class='input' type='text' placeholder='Email...' v-model='$parent.register.email'>
+					<input class='input' type='text' placeholder='Email...' v-model='$parent.register.email' autofocus>
 				</p>
 				<label class='label'>Username</label>
 				<p class='control'>
@@ -21,10 +21,10 @@
 					<input class='input' type='password' placeholder='Password...' v-model='$parent.register.password' v-on:keypress='$parent.submitOnEnter(submitModal, $event)'>
 				</p>
 				<div id="recaptcha"></div>
-				<p>By logging in you agree to our <a href="/terms" v-link="{ path: '/terms' }">Terms of Service</a> and <a href="/privacy" v-link="{ path: '/privacy' }">Privacy Policy</a>.</p>
+				<p>By logging in/registering you agree to our <a href="/terms" v-link="{ path: '/terms' }">Terms of Service</a> and <a href="/privacy" v-link="{ path: '/privacy' }">Privacy Policy</a>.</p>
 			</section>
 			<footer class='modal-card-foot'>
-				<a class='button is-primary' @click='submitModal()'>Submit</a>
+				<a class='button is-primary' href='#' @click='submitModal()'>Submit</a>
 				<a class='button is-github' :href='$parent.serverDomain + "/auth/github/authorize"'>
 					<div class='icon'>
 						<img class='invert' src='/assets/social/github.svg'/>
@@ -73,9 +73,14 @@
 
 <style type='scss' scoped>
 	.button.is-github {
-		background-color: #333 !important;
+		background-color: #333;
 		color: #fff !important;
 	}
 
+	.is-github:focus { background-color: #1a1a1a; }
+	.is-primary:focus { background-color: #028bca !important; }
+
 	.invert { filter: brightness(5); }
+
+	#recaptcha { padding: 10px 0; }
 </style>

+ 4 - 2
frontend/components/Modals/Report.vue

@@ -33,6 +33,7 @@
 									</div>
 								</article>
 							</div>
+							<a @click=highlight('previousSong') href='#' class='absolute-a'></a>
 						</div>
 					</div>
 					<div class='column song-type' v-if='$parent.currentSong !== {}'>
@@ -60,6 +61,7 @@
 									</div>
 								</article>
 							</div>
+							<a @click=highlight('currentSong') href='#' class='absolute-a'></a>
 						</div>
 					</div>
 				</div>
@@ -83,11 +85,11 @@
 				</div>
 			</section>
 			<footer class='modal-card-foot'>
-				<a class='button is-success' @click='create()'>
+				<a class='button is-success' @click='create()' href='#'>
 					<i class='material-icons save-changes'>done</i>
 					<span>&nbsp;Create</span>
 				</a>
-				<a class='button is-danger' @click='$parent.modals.report = !$parent.modals.report'>
+				<a class='button is-danger' @click='$parent.modals.report = !$parent.modals.report' href='#'>
 					<span>&nbsp;Cancel</span>
 				</a>
 			</footer>

+ 13 - 4
frontend/components/Sidebars/Playlist.vue

@@ -6,7 +6,7 @@
 			<aside class='menu' v-if='playlists.length > 0'>
 				<ul class='menu-list'>
 					<li v-for='playlist in playlists'>
-						<a href='#'>{{ playlist.displayName }}</a>
+						<span>{{ playlist.displayName }}</span>
 						<!--Will play playlist in community station Kris-->
 						<div class='icons-group'>
 							<a href='#' @click='selectPlaylist(playlist._id)' v-if="isNotSelected(playlist._id)">
@@ -22,7 +22,7 @@
 
 			<div class='none-found' v-else>No Playlists found</div>
 
-			<a class='button create-playlist' @click='$parent.toggleModal("createPlaylist")'>Create Playlist</a>
+			<a class='button create-playlist' href='#' @click='$parent.toggleModal("createPlaylist")'>Create Playlist</a>
 		</div>
 	</div>
 </template>
@@ -115,8 +115,15 @@
 		box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
 	}
 
+	.icons-group a {
+		display: flex;
+    	align-items: center;
+	}
+
+	.menu-list li { align-items: center; }
+
 	.inner-wrapper {	
-		top: 50px;
+		top: 64px;
 		position: relative;
 	}
 
@@ -140,12 +147,14 @@
     	margin-top: 20px;
 		height: 40px;
 		border-radius: 0;
-		background: rgb(3, 169, 244);
+		background: rgba(3, 169, 244, 1);
     	color: #fff !important;
 		border: 0;
 
 		&:active, &:focus { border: 0; }
 	}
 
+	.create-playlist:focus { background: #029ce3; }
+
 	.none-found { text-align: center; }
 </style>

+ 4 - 2
frontend/components/Sidebars/Queue.vue

@@ -32,7 +32,7 @@
 				</div>
 			</article>
 
-			<a class='button add-to-queue' @click='$parent.toggleModal("addSongToQueue")'>Add Song to Queue</a>
+			<a class='button add-to-queue' href='#' @click='$parent.toggleModal("addSongToQueue")'>Add Song to Queue</a>
 		</div>
 	</div>
 </template>
@@ -71,7 +71,7 @@
 	}
 
 	.inner-wrapper {	
-		top: 50px;
+		top: 64px;
 		position: relative;
 	}
 
@@ -102,6 +102,8 @@
 		&:active, &:focus { border: 0; }
 	}
 
+	.add-to-queue:focus { background: #029ce3; }
+
 	.media { padding: 0px 25px;}
 
 	.media-content .content {

+ 1 - 1
frontend/components/Sidebars/UsersList.vue

@@ -25,7 +25,7 @@
 	}
 
 	.inner-wrapper {	
-		top: 50px;
+		top: 64px;
 		position: relative;
 	}
 

+ 67 - 8
frontend/components/Station/Station.vue

@@ -13,13 +13,15 @@
 	<users-sidebar v-if='sidebars.users'></users-sidebar>
 	
 	<div class="station">
-		<div v-show="noSong" class="noSong">
-			<h1>No song is currently playing.</h1>
+		<div v-show="noSong" class="no-song">
+			<h1>No song is currently playing</h1>
 			<h4 v-if='type === "community" && station.partyMode'>
-				<a href='#' class='noSong' @click='sidebars.queue = true'>Add a Song to the Queue</a>
+				<a href='#' class='no-song' @click='sidebars.queue = true'>Add a Song to the Queue</a>
 			</h4>
-			<h1 v-if='type === "community" && !station.partyMode && $parent.userId === station.owner && !station.privatePlaylist'>Click <a href="#" @click="sidebars.playlist = true">here</a> to play a private playlist.</h1>
-			<h1 v-if='type === "community" && !station.partyMode && $parent.userId === station.owner && station.privatePlaylist'>Maybe you can add some songs to your selected private playlist.</h1>
+			<h4 v-if='type === "community" && !station.partyMode && $parent.userId === station.owner && !station.privatePlaylist'>
+				<a href='#' class='no-song' @click='sidebars.playlist = true'>Play a private playlist</a>
+			</h4>
+			<h1 v-if='type === "community" && !station.partyMode && $parent.userId === station.owner && station.privatePlaylist'>Maybe you can add some songs to your selected private playlist</h1>
 		</div>
 		<div class="columns is-mobile" v-show="!noSong">
 			<div class="column is-8-desktop is-offset-2-desktop is-12-mobile">
@@ -40,7 +42,7 @@
 						<h4 class="thin" style="margin-left: 0">{{currentSong.artists}}</h4>
 						<div class="columns is-mobile">
 							<form style="margin-top: 12px; margin-bottom: 0;" action="#" class="column is-7-desktop is-4-mobile">
-								<p style="margin-top: 0; position: relative; display: flex;">
+								<p class='volume-slider-wrapper'>
 									<i class="material-icons">volume_down</i>
 									<input type="range" id="volumeSlider" min="0" max="100" class="active" v-on:change="changeVolume()" v-on:input="changeVolume()">
 									<i class="material-icons">volume_up</i>
@@ -51,10 +53,12 @@
 									<li id="like" class="right" @click="toggleLike()">
 										<span class="flow-text">{{currentSong.likes}} </span>
 										<i id="thumbs_up" class="material-icons grey-text" v-bind:class="{ liked: liked }">thumb_up</i>
+										<a class='absolute-a behind' @click="toggleLike()" href='#'></a>
 									</li>
 									<li style="margin-right: 10px;" id="dislike" class="right" @click="toggleDislike()">
 										<span class="flow-text">{{currentSong.dislikes}} </span>
 										<i id="thumbs_down" class="material-icons grey-text" v-bind:class="{ disliked: disliked }">thumb_down</i>
+										<a class='absolute-a behind' @click="toggleDislike()" href='#'></a>
 									</li>
 								</ul>
 							</div>
@@ -117,7 +121,8 @@
 				timeBeforePause: 0,
 				station: {},
 				skipVotes: 0,
-				privatePlaylistQueueSelected: null
+				privatePlaylistQueueSelected: null,
+				automaticallyRequestedSongId: null
 			}
 		},
 		methods: {
@@ -281,6 +286,30 @@
 					if (data.status !== 'success') Toast.methods.addToast(`Error: ${data.message}`, 8000);
 				});
 			},
+			addFirstPrivatePlaylistSongToQueue: function() {
+				let _this = this;
+				let isInQueue = false;
+				let userId = _this.$parent.userId;
+				_this.queue.forEach((queueSong) => {
+					if (queueSong.requestedBy === userId) isInQueue = true;
+				});
+				if (!isInQueue && _this.privatePlaylistQueueSelected) {
+
+					_this.socket.emit('playlists.getFirstSong', _this.privatePlaylistQueueSelected, data => {
+						if (data.status === 'success') {
+							let songId = data.song._id;
+							_this.automaticallyRequestedSongId = songId;
+							_this.socket.emit('stations.addToQueue', _this.stationId, songId, data => {
+								if (data.status === 'success') {
+									_this.socket.emit('playlists.moveSongToBottom', _this.privatePlaylistQueueSelected, songId, data => {
+										if (data.status === 'success') {}
+									});
+								}
+							});
+						}
+					});
+				}
+			},
 			joinStation: function () {
 				let _this = this;
 				_this.socket.emit('stations.join', _this.stationId, res => {
@@ -369,6 +398,15 @@
 						if (_this.playerReady) _this.player.pauseVideo();
 						_this.noSong = true;
 					}
+
+					let isInQueue = false;
+					let userId = _this.$parent.userId;
+					_this.queue.forEach((queueSong) => {
+						if (queueSong.requestedBy === userId) isInQueue = true;
+					});
+					if (!isInQueue && _this.privatePlaylistQueueSelected && (_this.automaticallyRequestedSongId !== _this.currentSong._id || !_this.currentSong._id)) {
+						_this.addFirstPrivatePlaylistSongToQueue();
+					}
 				});
 
 				_this.socket.on('event:stations.pause', data => {
@@ -440,8 +478,10 @@
 				});
 			});
 
+			
 			let volume = parseInt(localStorage.getItem("volume"));
 			volume = (typeof volume === "number") ? volume : 20;
+			localStorage.setItem("volume", volume);
 			$("#volumeSlider").val(volume);
 		},
 		components: {
@@ -460,7 +500,7 @@
 </script>
 
 <style lang="scss">
-	.noSong {
+	.no-song {
 		color: #03A9F4;
 		text-align: center;
 	}
@@ -470,6 +510,13 @@
     	background: transparent;
 	}
 
+	.volume-slider-wrapper {
+		margin-top: 0;
+		position: relative;
+		display: flex;
+		align-items: center;
+	}
+
 	.stationDisplayName {
 		color: white !important;
 	}
@@ -766,4 +813,16 @@
 	}
 
 	.icons-group { display: flex; }
+
+	#like, #dislike {
+		position: relative;
+	}
+
+	.behind {
+		z-index: -1;
+	}
+
+	.behind:focus {
+		z-index: 0;
+	}
 </style>

+ 6 - 4
frontend/components/User/Settings.vue

@@ -51,21 +51,23 @@
 						Toast.methods.addToast('Your are currently not signed in', 3000);
 					}
 				});
+				_this.socket.on('event:user.username.changed', username => {
+					_this.$parent.username = username;
+				});
 			});
 		},
 		methods: {
 			changeEmail: function () {
 				if (!this.user.email.address) return Toast.methods.addToast('Email cannot be empty', 8000);
-
 				this.socket.emit('users.updateEmail', this.user.email.address, res => {
 					if (res.status !== 'success') Toast.methods.addToast(res.message, 8000);
 					else Toast.methods.addToast('Successfully changed email address', 4000);
 				});
 			},
 			changeUsername: function () {
-				if (!this.user.username) return Toast.methods.addToast('Username cannot be empty', 8000);
-
-				this.socket.emit('users.updateUsername', this.user.username, res => {
+				let _this = this;
+				if (!_this.user.username) return Toast.methods.addToast('Username cannot be empty', 8000);
+				_this.socket.emit('users.updateUsername', _this.user.username, res => {
 					if (res.status !== 'success') Toast.methods.addToast(res.message, 8000);
 					else Toast.methods.addToast('Successfully changed username', 4000);
 				});

+ 2 - 2
frontend/components/User/Show.vue

@@ -5,8 +5,8 @@
 			<img class="avatar" src="/assets/notes.png"/>
 			<h2 class="has-text-centered username">@{{user.username}}</h2>
 			<div class="admin-functionality" v-if="user.role == 'admin'">
-				<a class="button is-small is-info is-outlined" @click="changeRank('admin')" v-if="user.role == 'default'">Promote to Admin</a>
-				<a class="button is-small is-danger is-outlined" @click="changeRank('default')" v-else>Demote to User</a>
+				<a class="button is-small is-info is-outlined" href='#' @click="changeRank('admin')" v-if="user.role == 'default'">Promote to Admin</a>
+				<a class="button is-small is-danger is-outlined" href='#' @click="changeRank('default')" v-else>Demote to User</a>
 			</div>
 			<nav class="level">
 				<div class="level-item has-text-centered">

+ 18 - 29
frontend/components/pages/Home.vue

@@ -24,10 +24,11 @@
 						{{ station.description }}
 					</div>
 				</div>
+				<a @click="this.$dispatch('joinStation', station._id)" href='#' class='absolute-a'></a>
 			</div>
 		</div>
 		<div class="group">
-			<div class="group-title">Community Stations <i class="material-icons ccs-button" @click="toggleModal('createCommunityStation')" v-if="$parent.loggedIn">add_circle_outline</i></div>
+			<div class="group-title">Community Stations <a @click="toggleModal('createCommunityStation')" v-if="$parent.loggedIn" href='#'><i class="material-icons community-button">add_circle_outline</i></a></div>
 			<div class="card station-card" v-for="station in stations.community" v-link="{ path: '/community/' + station._id }" @click="this.$dispatch('joinStation', station._id)" :class="station.class">
 				<div class="card-image">
 					<figure class="image is-square">
@@ -49,6 +50,7 @@
 						{{ station.description }}
 					</div>
 				</div>
+				<a @click="this.$dispatch('joinStation', station._id)" href='#' class='absolute-a'></a>
 			</div>
 		</div>
 		<main-footer></main-footer>
@@ -86,6 +88,7 @@
 					});
 					_this.socket.on('event:stations.created', station => {
 						if (!station.currentSong) station.currentSong = { thumbnail: '/assets/notes-transparent.png' };
+						if (station.currentSong && !station.currentSong.thumbnail) station.currentSong.thumbnail = "/assets/notes-transparent.png";
 						if (station.privacy !== 'public') {
 							station.class = {'station-red': true}
 						} else if (station.type === 'community') {
@@ -110,6 +113,7 @@
 						_this.stations.official = [];
 						if (data.status === "success") data.stations.forEach(station => {
 							if (!station.currentSong) station.currentSong = { thumbnail: '/assets/notes-transparent.png' };
+							if (station.currentSong && !station.currentSong.thumbnail) station.currentSong.thumbnail = "/assets/notes-transparent.png";
 							if (station.privacy !== 'public') {
 								station.class = { 'station-red': true }
 							} else if (station.type === 'community') {
@@ -149,53 +153,37 @@
 	}
 
 	@media only screen and (min-width: 1200px) {
-		html {
-			font-size: 15px;
-		}
+		html { font-size: 15px; }
 	}
 
 	@media only screen and (min-width: 992px) {
-		html {
-			font-size: 14.5px;
-		}
+		html { font-size: 14.5px; }
 	}
 
 	@media only screen and (min-width: 0) {
-		html {
-			font-size: 14px;
-		}
+		html { font-size: 14px; }
 	}
 
-	.group {
-		min-height: 64px;
-	}
+	.group { min-height: 64px; }
 
 	.station-card {
 		margin: 10px;
 		cursor: pointer;
 	}
 
-	.ccs-button {
+	.community-button {
 		cursor: pointer;
 		transition: .25s ease color;
 		font-size: 30px;
+		color: black;
 	}
 
-	.ccs-button:hover {
-		color: #03a9f4;
-	}
+	.community-button:hover { color: #03a9f4; }
 
-	.station-blue {
-		outline: 5px solid #03a9f4;
-	}
-
-	.station-red {
-		outline: 5px solid #f45703;
-	}
+	.station-blue { outline: 5px solid #03a9f4; }
+	.station-red { outline: 5px solid #f45703; }
 
-	.label {
-		display: flex;
-	}
+	.label { display: flex; }
 
 	.g-recaptcha {
 		display: flex;
@@ -221,8 +209,8 @@
 	}
 
 	.group .card {
-		display: inline-block;
-		height: 415px;
+		display: inline-flex;
+    	flex-direction: column;
 		overflow: hidden;
 	}
 
@@ -232,6 +220,7 @@
 	}
 
 	.content {
+		text-align: left;
 		word-wrap: break-word;
 	}
 	

+ 0 - 3
frontend/io.js

@@ -36,19 +36,16 @@ export default {
 	init: function (url) {
 		this.socket = window.socket = io(url);
 		this.socket.on('connect', () => {
-			// Connect
 			onConnectCallbacks.forEach((cb) => {
 				cb();
 			});
 		});
 		this.socket.on('disconnect', () => {
-			// Disconnect
 			onDisconnectCallbacks.forEach((cb) => {
 				cb();
 			});
 		});
 		this.socket.on('connect_error', () => {
-			// Connect error
 			onConnectErrorCallbacks.forEach((cb) => {
 				cb();
 			});