diff --git a/frontendJS/respotify/.eslintrc b/frontendJS/respotify/.eslintrc
new file mode 100644
index 0000000..09af502
--- /dev/null
+++ b/frontendJS/respotify/.eslintrc
@@ -0,0 +1,7 @@
+{
+ "extends": "airbnb",
+ "rules": {
+ "arrow-body-style": 0,
+ "no-param-reassign": 0
+ }
+}
\ No newline at end of file
diff --git a/frontendJS/respotify/package.json b/frontendJS/respotify/package.json
index a092c5b..4f8d775 100644
--- a/frontendJS/respotify/package.json
+++ b/frontendJS/respotify/package.json
@@ -1,27 +1,39 @@
{
"name": "respotify",
"version": "1.0.0",
- "description": "",
+ "description": "The webapp accompanying the React tutorial on patternhatch.com",
"main": "index.js",
"scripts": {
"start": "webpack-dev-server --hot --inline",
"build": "webpack",
"test": "echo \"Error: no test specified\" && exit 1"
},
- "keywords": [],
- "author": "",
- "license": "ISC",
+ "keywords": [
+ "react",
+ "redux",
+ "webpack",
+ "babel"
+ ],
+ "author": "Nitin Punjabi",
+ "license": "MIT",
"devDependencies": {
"babel-core": "^6.10.4",
"babel-loader": "^6.2.4",
"babel-preset-es2015": "^6.9.0",
"babel-preset-react": "^6.11.1",
+ "eslint": "^2.13.1",
+ "eslint-config-airbnb": "^9.0.1",
+ "eslint-loader": "^1.4.0",
+ "eslint-plugin-import": "^1.10.2",
+ "eslint-plugin-jsx-a11y": "^1.5.5",
+ "eslint-plugin-react": "^5.2.2",
"file-loader": "^0.9.0",
"react-hot-loader": "^1.3.0",
"webpack": "^1.13.1",
"webpack-dev-server": "^1.14.1"
},
"dependencies": {
+ "axios": "^0.13.1",
"react": "^15.2.0",
"react-dom": "^15.2.0"
}
diff --git a/frontendJS/respotify/src/api/musicApi.js b/frontendJS/respotify/src/api/musicApi.js
new file mode 100644
index 0000000..20e7a3d
--- /dev/null
+++ b/frontendJS/respotify/src/api/musicApi.js
@@ -0,0 +1,17 @@
+import axios from 'axios';
+
+function fetch(request, callback) {
+ axios.get(request).then(response => {
+ callback(response.data);
+ });
+}
+
+export function getAlbums(artist, callback) {
+ const request = `https://api.spotify.com/v1/search?q=${artist}&type=album`;
+ fetch(request, callback);
+}
+
+export function getTracks(albumId, callback) {
+ const request = `https://api.spotify.com/v1/albums/${albumId}`;
+ fetch(request, callback);
+}
\ No newline at end of file
diff --git a/frontendJS/respotify/src/components/Album.js b/frontendJS/respotify/src/components/Album.js
new file mode 100644
index 0000000..e04da4f
--- /dev/null
+++ b/frontendJS/respotify/src/components/Album.js
@@ -0,0 +1,27 @@
+import React from 'react';
+
+const Album = (props) => {
+ return (
+
+ props.getTracks(props.album.id)}
+ />
+
+ );
+};
+
+Album.propTypes = {
+ album: React.PropTypes.object.isRequired,
+ getTracks: React.PropTypes.func.isRequired,
+};
+
+Album.styles = {
+ img: {
+ marginBottom: '1em',
+ },
+};
+
+export default Album;
\ No newline at end of file
diff --git a/frontendJS/respotify/src/components/AlbumList.js b/frontendJS/respotify/src/components/AlbumList.js
new file mode 100644
index 0000000..a3b5001
--- /dev/null
+++ b/frontendJS/respotify/src/components/AlbumList.js
@@ -0,0 +1,36 @@
+import React from 'react';
+
+import Album from './Album';
+
+const AlbumList = (props) => {
+ const albums = props.albums.map((album) =>
+ );
+
+ return (
+
+ );
+};
+
+AlbumList.propTypes = {
+ albums: React.PropTypes.array.isRequired,
+ getTracks: React.PropTypes.func.isRequired,
+};
+
+AlbumList.styles = {
+ div: {
+ width: 370,
+ marginLeft: 30,
+ textAlign: 'right',
+ maxHeight: '85vh',
+ overflowY: 'auto',
+ },
+ ul: {
+ listStyle: 'none',
+ },
+};
+
+export default AlbumList;
\ No newline at end of file
diff --git a/frontendJS/respotify/src/components/SearchBar.js b/frontendJS/respotify/src/components/SearchBar.js
new file mode 100644
index 0000000..8214e7a
--- /dev/null
+++ b/frontendJS/respotify/src/components/SearchBar.js
@@ -0,0 +1,53 @@
+import React from 'react';
+
+class SearchBar extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ searchTerm: '',
+ };
+ this.handleInputChange = this.handleInputChange.bind(this);
+ this.handleKeyPress = this.handleKeyPress.bind(this);
+ }
+
+ handleInputChange(event) {
+ this.setState({
+ searchTerm: event.target.value,
+ });
+ }
+
+ handleKeyPress(event) {
+ if (event.key === 'Enter') {
+ this.props.getAlbums(this.state.searchTerm);
+ }
+ }
+
+ render() {
+ return (
+
+
Search for an Artist
+
+
+ );
+ }
+}
+
+SearchBar.propTypes = {
+ getAlbums: React.PropTypes.func.isRequired,
+};
+
+SearchBar.styles = {
+ div: {
+ margin: 30,
+ textAlign: 'center',
+ },
+ input: {
+ width: '60%',
+ },
+};
+
+export default SearchBar;
\ No newline at end of file
diff --git a/frontendJS/respotify/src/components/Track.js b/frontendJS/respotify/src/components/Track.js
new file mode 100644
index 0000000..54c7003
--- /dev/null
+++ b/frontendJS/respotify/src/components/Track.js
@@ -0,0 +1,33 @@
+import React from 'react';
+
+const mouseOverColor = '#add8e6';
+const mouseOutColor = 'white';
+
+const Track = (props) => {
+ return (
+ {e.target.style.backgroundColor = mouseOverColor}}
+ onMouseOut={(e) => {e.target.style.backgroundColor = mouseOutColor}}
+ onClick={() => props.playPreview(props.track.preview_url)}
+ >
+ {props.track.name}
+
+ );
+};
+
+Track.propTypes = {
+ track: React.PropTypes.object.isRequired,
+ playPreview: React.PropTypes.func.isRequired,
+};
+
+Track.styles = {
+ li: {
+ fontSize: '1.5em',
+ padding: '0.2em',
+ listStyleType: 'none',
+ backgroundColor: mouseOutColor,
+ },
+};
+
+export default Track;
\ No newline at end of file
diff --git a/frontendJS/respotify/src/components/TrackList.js b/frontendJS/respotify/src/components/TrackList.js
new file mode 100644
index 0000000..eeed8e7
--- /dev/null
+++ b/frontendJS/respotify/src/components/TrackList.js
@@ -0,0 +1,23 @@
+import React from 'react';
+
+import Track from './Track';
+
+const TrackList = (props) => {
+ const tracks = props.tracks.map((track) =>
+ );
+
+ return (
+
+ );
+};
+
+TrackList.propTypes = {
+ tracks: React.PropTypes.array.isRequired,
+ playPreview: React.PropTypes.func.isRequired,
+};
+
+export default TrackList;
\ No newline at end of file
diff --git a/frontendJS/respotify/src/greeting.js b/frontendJS/respotify/src/greeting.js
deleted file mode 100644
index 0f3f0c9..0000000
--- a/frontendJS/respotify/src/greeting.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import React from "react";
-
-export default React.createClass({
- render: function() {
- return (
-
- Hello, {this.props.name}!
-
- )
- }
-})
\ No newline at end of file
diff --git a/frontendJS/respotify/src/index.html b/frontendJS/respotify/src/index.html
index a889f34..18cb0db 100644
--- a/frontendJS/respotify/src/index.html
+++ b/frontendJS/respotify/src/index.html
@@ -3,9 +3,9 @@
Respotify
+
- Respotify
diff --git a/frontendJS/respotify/src/index.js b/frontendJS/respotify/src/index.js
index 4f0df5b..dc7d2d5 100644
--- a/frontendJS/respotify/src/index.js
+++ b/frontendJS/respotify/src/index.js
@@ -1,8 +1,73 @@
-import React from "react";
-import ReactDOM from "react-dom";
-import Greeting from "./greeting";
+import React from 'react';
+import ReactDOM from 'react-dom';
+
+import SearchBar from './components/SearchBar';
+import AlbumList from './components/AlbumList';
+import TrackList from './components/TrackList';
+import * as musicApi from './api/musicApi';
+
+class App extends React.Component {
+ constructor() {
+ super();
+ this.state = ({
+ albums: [],
+ tracks: [],
+ currentPreview: null,
+ });
+ this.getAlbums = this.getAlbums.bind(this);
+ this.processAlbums = this.processAlbums.bind(this);
+ this.getTracks = this.getTracks.bind(this);
+ this.processTracks = this.processTracks.bind(this);
+ this.playPreview = this.playPreview.bind(this);
+ }
+
+ getAlbums(artist) {
+ musicApi.getAlbums(artist, this.processAlbums);
+ }
+
+ getTracks(albumId) {
+ musicApi.getTracks(albumId, this.processTracks);
+ }
+
+ processAlbums(payload) {
+ this.setState({
+ albums: payload.albums.items,
+ tracks: [],
+ });
+ }
+
+ processTracks(payload) {
+ this.setState({
+ tracks: payload.tracks.items,
+ });
+ }
+
+ playPreview(previewUrl) {
+ if (this.state.currentPreview) {
+ const curAudioObject = this.state.currentPreview;
+ curAudioObject.pause();
+ }
+
+ const newAudioObject = new Audio(previewUrl);
+ this.setState({
+ currentPreview: newAudioObject
+ });
+
+ newAudioObject.play();
+ }
+
+ render() {
+ return (
+
+ );
+ }
+}
ReactDOM.render(
- ,
+ ,
document.getElementById('container')
);
\ No newline at end of file
diff --git a/frontendJS/respotify/webpack.config.js b/frontendJS/respotify/webpack.config.js
index e1168e5..cde0139 100644
--- a/frontendJS/respotify/webpack.config.js
+++ b/frontendJS/respotify/webpack.config.js
@@ -20,7 +20,17 @@ module.exports = {
devServer: {
contentBase: PATHS.dist
},
+ eslint: {
+ emitWarning: true
+ },
module: {
+ preloaders: [
+ {
+ test: /\.js$/,
+ loaders: ['eslint-loader'],
+ exclude: /node_modules/
+ }
+ ],
loaders: [
{
test: /\.html$/,