Skip to content

Commit

Permalink
Push project
Browse files Browse the repository at this point in the history
  • Loading branch information
JMPerez committed Aug 21, 2017
1 parent 993aee8 commit 993b7ac
Show file tree
Hide file tree
Showing 57 changed files with 2,397 additions and 2 deletions.
17 changes: 17 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# See editorconfig.org

root = true

[*]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[**.js]
indent_style = space
indent_size = 2

[**.json]
indent_style = space
indent_size = 2
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.next
node_modules
queue.json
package-lock.json
1 change: 1 addition & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
web: npm run build && npm run start
27 changes: 25 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,25 @@
# c
A collaborative listening room using Spotify
# C - A collaborative listening room using Spotify

This project is a site where multiple users can propose songs and vote for them, having them played in a synchronised way through Spotify.

## Setting up

The server can be run locally and also deployed to Heroku. You will need to register your own Spotify app and set the credentials in a couple of config files. For that:

1. Create an application on [Spotify's Developer Site](https://developer.spotify.com/my-applications/).

2. Add as redirect uris both http://localhost:3000/auth/callback (for development) and <production_domain>/auth/callback (if you want to deploy your app somewhere).

3. Set your HOST in `config/app.js`.

4. Set your CLIENT_ID and CLIENT_SECRET in `config/auth.js`.

## Dependencies

Install the dependencies running `npm install`.

## Running

During development, run `npm run dev`.

When running on production, run `web: npm run build && npm run start`.
25 changes: 25 additions & 0 deletions actions/devicesActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as types from '../constants/ActionTypes';

export const fetchAvailableDevices = () => ({
type: types.FETCH_AVAILABLE_DEVICES
});
export const fetchAvailableDevicesSuccess = list => ({
type: types.FETCH_AVAILABLE_DEVICES_SUCCESS,
list
});
export const fetchAvailableDevicesError = error => ({
type: types.FETCH_AVAILABLE_DEVICES_ERROR,
error
});

export const transferPlaybackToDevice = deviceId => ({
type: types.TRANSFER_PLAYBACK_TO_DEVICE,
deviceId
});
export const transferPlaybackToDeviceSuccess = list => ({
type: types.TRANSFER_PLAYBACK_TO_DEVICE_SUCCESS
});
export const transferPlaybackToDeviceError = list => ({
type: types.TRANSFER_PLAYBACK_TO_DEVICE_ERROR,
error
});
31 changes: 31 additions & 0 deletions actions/playbackActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import fetch from 'isomorphic-unfetch';

import Config from '../config/app';
import * as types from '../constants/ActionTypes';

// playback
export const playTrack = (track, user, position) => ({
type: types.PLAY_TRACK,
track,
user,
position
});
export const playTrackSuccess = (track, user, position) => ({
type: types.PLAY_TRACK_SUCCESS,
track,
user,
position
});

export const mutePlayback = () => ({ type: types.MUTE_PLAYBACK });
export const unmutePlayback = () => ({ type: types.UNMUTE_PLAYBACK });

export const fetchPlayingContextSuccess = playingContext => ({
type: types.FETCH_PLAYING_CONTEXT_SUCCESS,
playingContext
});

export const fetchPlayingContext = () => dispatch =>
fetch(`${Config.HOST}/api/now-playing`)
.then(res => res.json())
.then(res => dispatch(fetchPlayingContextSuccess(res)));
15 changes: 15 additions & 0 deletions actions/queueActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import fetch from 'isomorphic-unfetch';

import Config from '../config/app';
import * as types from '../constants/ActionTypes';

export const queueTrack = id => ({ type: types.QUEUE_TRACK, id });
export const updateQueue = queue => ({ type: types.UPDATE_QUEUE, data: queue });
export const queueEnded = () => ({ type: types.QUEUE_ENDED });
export const queueRemoveTrack = id => ({
type: types.QUEUE_REMOVE_TRACK,
id
});

export const fetchQueue = () => dispatch =>
fetch(`${Config.HOST}/api/queue`).then(res => res.json()).then(res => dispatch(updateQueue(res)));
14 changes: 14 additions & 0 deletions actions/searchActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as types from '../constants/ActionTypes';

export const searchTracks = query => ({ type: types.SEARCH_TRACKS, query });
export const searchTracksSuccess = (query, results) => ({
type: types.SEARCH_TRACKS_SUCCESS,
query,
results
});
export const searchTracksReset = () => ({ type: types.SEARCH_TRACKS_RESET });
export const fetchTrack = id => ({ type: types.FETCH_TRACK, id });
export const fetchTrackSuccess = (id, track) => ({
type: types.FETCH_TRACK_SUCCESS,
id
});
23 changes: 23 additions & 0 deletions actions/sessionActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as types from '../constants/ActionTypes';

export const load = () => ({ type: types.LOAD });
export const login = () => ({ type: types.LOGIN });
export const loginSuccess = () => ({
type: types.LOGIN_SUCCESS
});
export const loginFailure = refresh_token => ({
type: types.LOGIN_FAILURE,
refresh_token
});
export const updateToken = refreshToken => ({
type: types.UPDATE_TOKEN,
refreshToken
});
export const updateTokenSuccess = access_token => ({
type: types.UPDATE_TOKEN_SUCCESS,
access_token
});
export const updateCurrentUser = user => ({
type: types.UPDATE_CURRENT_USER,
user
});
9 changes: 9 additions & 0 deletions actions/usersActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import fetch from 'isomorphic-unfetch';

import Config from '../config/app';
import * as types from '../constants/ActionTypes';

export const updateUsers = users => ({ type: types.UPDATE_USERS, data: users });

export const fetchUsers = () => dispatch =>
fetch(`${Config.HOST}/api/users`).then(res => res.json()).then(res => dispatch(updateUsers(res)));
10 changes: 10 additions & 0 deletions actions/voteActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as types from '../constants/ActionTypes';

export const voteUp = id => ({
type: types.VOTE_UP,
id
});

export const voteUpSuccess = () => ({
type: types.VOTE_UP_SUCCESS
});
143 changes: 143 additions & 0 deletions components/AddToQueue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { injectIntl } from 'react-intl';

import { searchTracks, searchTracksReset } from '../actions/searchActions';
import { queueTrack } from '../actions/queueActions';

class ResultsList extends Component {
render() {
const { results, focus } = this.props;
return (
<ul className="add-to-queue__search-results">
<style jsx>{`
.add-to-queue__search-results {
border: 1px solid #999;
list-style: none;
margin: 0;
padding: 0;
}
.add-to-queue__search-results-item {
padding: 5px 0 5px 5px;
}
.add-to-queue__search-results-item--focused {
background-color: #eee;
}
`}</style>
{results.map((r, index) => {
const isFocused = focus === index;
const className =
'add-to-queue__search-results-item' + (isFocused ? ' add-to-queue__search-results-item--focused' : '');
return (
<li key={r.id} className={className} onClick={() => this.props.onSelect(r.id)}>
{r.name} - {r.artists[0].name}
</li>
);
})}
</ul>
);
}
}

class AddToQueue extends Component {
state = {
text: this.props.text || '',
focus: -1
};

handleChange = e => {
const text = e.target.value;
this.setState({ text: text });
if (text !== '') {
this.props.searchTracks(text);
} else {
this.setState({ focus: -1 });
this.props.searchTracksReset();
}
};

handleSelectElement = id => {
this.setState({ text: '' });
this.props.queueTrack(id);
this.props.searchTracksReset();
};

handleBlur = e => {
// todo: this happens before the item from the list is selected, hiding the
// list of results. We need to do this in a different way.
/* this.setState({ focus: -1 });
this.props.searchTracksReset(); */
};

handleFocus = e => {
if (e.target.value !== '') {
this.props.searchTracks(e.target.value);
}
};

handleKeyDown = e => {
switch (e.keyCode) {
case 38: // up
this.setState({ focus: this.state.focus - 1 });
break;
case 40: // down
this.setState({ focus: this.state.focus + 1 });
break;
case 13: {
let correct = false;
if (this.state.focus !== -1) {
this.props.queueTrack(this.props.search.results[this.state.focus].id);
correct = true;
} else {
const text = e.target.value.trim();
if (text.length !== 0) {
this.props.queueTrack(text);
correct = true;
}
}
if (correct) {
this.setState({ text: '' });
this.props.searchTracksReset();
this.setState({ focus: -1 });
}
break;
}
}
};

render() {
const placeholder = this.props.intl.formatMessage({id: 'queue.add'});
const results = this.props.search.results;
return (
<div className="add-to-queue" onBlur={this.handleBlur}>
<style jsx>{`
.add-to-queue__input {
padding: 5px;
width: 400px;
}
`}</style>
<input
className="add-to-queue__input"
placeholder={placeholder}
value={this.state.text}
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
onFocus={this.handleFocus}
/>
{results && <ResultsList results={results} onSelect={this.handleSelectElement} focus={this.state.focus} />}
</div>
);
}
}

const mapDispatchToProps = dispatch => ({
queueTrack: text => dispatch(queueTrack(text)),
searchTracks: query => dispatch(searchTracks(query)),
searchTracksReset: () => dispatch(searchTracksReset())
});

const mapStateToProps = state => ({
search: state.search
});

export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(AddToQueue));
15 changes: 15 additions & 0 deletions components/ButtonStyle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const ButtonStyle = `.btn {
background-color: transparent;
border: 1px solid #666;
border-radius: 50px;
color: #666;
cursor: pointer;
line-height: 28px;
padding: 0 15px;
}`;

export const ButtonDarkStyle = `.btn--dark {
background-color: #bbc8d5;
border: 1px solid #bbc8d5;
color: #333;
}`;
Loading

0 comments on commit 993b7ac

Please sign in to comment.