diff --git a/src/js/components/FollowButton.js b/src/js/components/FollowButton.js
index c0d0fb16..cc01a3e5 100755
--- a/src/js/components/FollowButton.js
+++ b/src/js/components/FollowButton.js
@@ -42,6 +42,7 @@ const mapStateToProps = (state, ownProps) => {
return {
spotify_authorized: state.spotify.authorized,
artist: state.ui.artist,
+ user: state.ui.user,
album: state.ui.album,
playlist: state.ui.playlist
}
diff --git a/src/js/index.js b/src/js/index.js
index 491fad21..a3785265 100755
--- a/src/js/index.js
+++ b/src/js/index.js
@@ -16,6 +16,7 @@ import App from './views/App'
import Album from './views/Album'
import Artist from './views/Artist'
import Playlist from './views/Playlist'
+import User from './views/User'
import Queue from './views/Queue'
import Settings from './views/Settings'
import Search from './views/Search'
@@ -49,6 +50,7 @@ ReactDOM.render(
+
diff --git a/src/js/services/spotify/actions.js b/src/js/services/spotify/actions.js
index 022fe6b6..012bef45 100755
--- a/src/js/services/spotify/actions.js
+++ b/src/js/services/spotify/actions.js
@@ -383,6 +383,14 @@ export function following(uri, method = 'GET'){
data = {}
}
break
+ case 'user':
+ if( method == 'GET' ){
+ endpoint = 'me/following/contains?type=user&ids='+ helpers.getFromUri('userid', uri)
+ }else{
+ endpoint = 'me/following?type=user&ids='+ helpers.getFromUri('userid', uri)
+ data = {}
+ }
+ break
case 'playlist':
if( method == 'GET' ){
endpoint = 'users/'+ helpers.getFromUri('userid',uri) +'/playlists/'+ helpers.getFromUri('playlistid',uri) +'/followers/contains?ids='+ getState().spotify.me.id
@@ -496,6 +504,43 @@ export function getLibraryArtists(){
+/**
+ * =============================================================== USER(S) ==============
+ * ======================================================================================
+ **/
+
+export function getUser( uri ){
+ return (dispatch, getState) => {
+
+ dispatch({ type: 'SPOTIFY_USER_LOADED', data: false });
+
+ var user = {};
+
+ // get both the artist and the top tracks
+ $.when(
+
+ sendRequest( dispatch, getState, 'users/'+ helpers.getFromUri('userid',uri) )
+ .then( response => {
+ Object.assign(user, response);
+ }),
+
+ sendRequest( dispatch, getState, 'users/'+ helpers.getFromUri('userid', uri) +'/playlists?limit=50' )
+ .then( response => {
+ Object.assign(user, { playlists: response.items, playlists_more: response.next, playlists_total: response.total });
+ })
+
+ ).then( () => {
+ dispatch({
+ type: 'SPOTIFY_USER_LOADED',
+ data: user
+ });
+ });
+ }
+}
+
+
+
+
/**
* =============================================================== ALBUM(S) =============
* ======================================================================================
@@ -715,5 +760,4 @@ export function reorderPlaylistTracks( uri, range_start, range_length, insert_be
});
});
}
-}
-
+}
\ No newline at end of file
diff --git a/src/js/services/ui/reducer.js b/src/js/services/ui/reducer.js
index 406b839a..d8758145 100755
--- a/src/js/services/ui/reducer.js
+++ b/src/js/services/ui/reducer.js
@@ -174,6 +174,26 @@ export default function reducer(ui = {}, action){
return Object.assign({}, ui, { artist: artist, following_loading: false });
+ /**
+ * User
+ **/
+
+ case 'SPOTIFY_USER_LOADED':
+ if( !action.data ) return Object.assign({}, ui, { user: false })
+ return Object.assign({}, ui, { user: action.data })
+
+ case 'SPOTIFY_USER_PLAYLISTS_LOADED_MORE':
+ var user = Object.assign({}, ui.user, {
+ playlists: [ ...ui.user.playlists, ...action.data.items ],
+ playlists_more: action.data.next
+ })
+ return Object.assign({}, ui, { user: user });
+
+ case 'USER_FOLLOWING_LOADED':
+ var user = Object.assign({}, ui.user, { following: action.is_following })
+ return Object.assign({}, ui, { user: user, following_loading: false });
+
+
/**
* Playlists
**/
diff --git a/src/js/views/Playlist.js b/src/js/views/Playlist.js
index 9f88d3f5..5c8e9000 100755
--- a/src/js/views/Playlist.js
+++ b/src/js/views/Playlist.js
@@ -127,7 +127,7 @@ class Playlist extends React.Component{
{ this.props.playlist.tracks_total } tracks,
{ this.props.playlist.last_modified ? Updated ago : null }
{ this.props.playlist.followers ? {this.props.playlist.followers.total.toLocaleString()} followers : null }
- { scheme == 'spotify' && this.props.playlist.owner ? By {this.props.playlist.owner.id} { !this.props.playlist.public ? : null } : null }
+ { scheme == 'spotify' && this.props.playlist.owner ? By {this.props.playlist.owner.id} { !this.props.playlist.public ? : null } : null }
{ scheme == 'spotify' ? Spotify playlist : null }
{ scheme == 'm3u' ? Local playlist : null }
diff --git a/src/js/views/User.js b/src/js/views/User.js
new file mode 100755
index 00000000..bdecedb3
--- /dev/null
+++ b/src/js/views/User.js
@@ -0,0 +1,95 @@
+
+import React, { PropTypes } from 'react'
+import { connect } from 'react-redux'
+import { bindActionCreators } from 'redux'
+import FontAwesome from 'react-fontawesome'
+
+import Thumbnail from '../components/Thumbnail'
+import PlaylistGrid from '../components/PlaylistGrid'
+import FollowButton from '../components/FollowButton'
+import LazyLoadListener from '../components/LazyLoadListener'
+
+import * as helpers from '../helpers'
+import * as mopidyActions from '../services/mopidy/actions'
+import * as spotifyActions from '../services/spotify/actions'
+
+class User extends React.Component{
+
+ constructor(props) {
+ super(props);
+ }
+
+ componentDidMount(){
+ this.loadUser();
+ }
+
+ componentWillReceiveProps( nextProps ){
+ if( nextProps.params.uri != this.props.params.uri ) this.loadUser( nextProps )
+ }
+
+ loadUser( props = this.props ){
+ this.props.spotifyActions.getUser( props.params.uri )
+ }
+
+ loadMore(){
+ if( !this.props.user.playlists_more ) return
+ this.props.spotifyActions.getURL( this.props.user.playlists_more, 'SPOTIFY_USER_PLAYLISTS_LOADED_MORE' );
+ }
+
+ renderMeFlag(){
+ if( !this.props.spotify_authorized ) return null
+
+ if( helpers.getFromUri('userid',this.props.params.uri) == this.props.me.id ){
+ return You
+ }else{
+ return null
+ }
+ }
+
+ render(){
+ if( !this.props.user ) return null
+
+ return (
+
+
+
+
+
{ this.props.user.display_name ? this.props.user.display_name : this.props.user.id }{ this.renderMeFlag() }
+
+
+
+
+
+
+ - { this.props.user.playlists_total } playlists
+ - { this.props.user.followers.total.toLocaleString() } followers
+
+
+
+
+
+ { this.props.user.playlists ? : null }
+ this.loadMore() }/>
+
+
+
+
+ );
+ }
+}
+
+const mapStateToProps = (state, ownProps) => {
+ return {
+ spotify_authorized: state.spotify.authorized,
+ me: state.spotify.me,
+ user: state.ui.user
+ };
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ spotifyActions: bindActionCreators(spotifyActions, dispatch)
+ }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(User)
\ No newline at end of file
diff --git a/src/scss/app.scss b/src/scss/app.scss
index b13a1157..2000d62d 100755
--- a/src/scss/app.scss
+++ b/src/scss/app.scss
@@ -20,6 +20,7 @@
@import 'components/header';
@import 'views/artist';
+@import 'views/user';
@import 'views/album';
@import 'views/playlist';
@import 'views/queue';
diff --git a/src/scss/global/_core.scss b/src/scss/global/_core.scss
index b13168a6..90d191f4 100755
--- a/src/scss/global/_core.scss
+++ b/src/scss/global/_core.scss
@@ -144,3 +144,17 @@ footer {
padding-bottom: 4px;
}
}
+
+.flag {
+ font-size: 12px;
+ display: inline-block;
+ padding: 2px 4px;
+ border-radius: 2px;
+ background: $light_grey;
+ color: $dark_grey;
+
+ &.blue {
+ background: $blue;
+ color: #FFFFFF;
+ }
+}
diff --git a/src/scss/views/_user.scss b/src/scss/views/_user.scss
new file mode 100755
index 00000000..6c1645d7
--- /dev/null
+++ b/src/scss/views/_user.scss
@@ -0,0 +1,23 @@
+
+.user-view {
+ .intro {
+ @include clearfix();
+ padding-bottom: 20px;
+
+ .thumbnail {
+ max-width: 200px;
+ margin: 40px 40px 0 40px;
+ float: left;
+ }
+
+ h1 {
+ padding-top: 80px;
+ padding-bottom: 20px;
+ }
+
+ .actions {
+ float: left;
+ margin-right: 20px;
+ }
+ }
+}