diff --git a/app.js b/app.js index 95f5da10c..04c3e2ff9 100644 --- a/app.js +++ b/app.js @@ -4,39 +4,44 @@ const path = require('path'); const cookieParser = require('cookie-parser'); const logger = require('morgan'); const hbs = require('hbs'); - -// require spotify-web-api-node package here: - +const hbsutils = require('hbs-utils')(hbs); // Load hbs-utils (watches partials for nodemon) const indexRouter = require('./routes/index'); +const app = express(); - const app = express(); - - // view engine setup +// View engine setup app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'hbs'); - app.use(logger('dev')); +// Initialize hbs-util +hbsutils.registerPartials(__dirname + '/views/partials'); +hbsutils.registerWatchedPartials(__dirname + '/views/partials'); + +app.use(logger('dev')); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(cookieParser()); app.use(express.static(path.join(__dirname, 'public'))); +app.use('/', indexRouter); - app.use('/', indexRouter); - - // catch 404 and forward to error handler +// Catch 404 and forward to error handler app.use((req, res, next) => { next(createError(404)); }); - // error handler +// Error handler app.use((err, req, res, next) => { // set locals, only providing error in development res.locals.message = err.message; res.locals.error = req.app.get('env') === 'development' ? err : {}; - // render the error page + // Render the error page res.status(err.status || 500); res.render('error'); }); - module.exports = app; \ No newline at end of file + module.exports = app; + + // Start server in port 3000 + app.listen(3000, () => { + console.log('Server listening on port 3000') + }); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 5232584c8..12eb0b996 100644 --- a/package-lock.json +++ b/package-lock.json @@ -916,6 +916,15 @@ "walk": "2.3.9" } }, + "hbs-utils": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/hbs-utils/-/hbs-utils-0.0.4.tgz", + "integrity": "sha1-8Cur+QyhJllCeoMfb8btCb2VWpI=", + "requires": { + "node-watch": "^0.4.1", + "walk": "^2.3.9" + } + }, "hosted-git-info": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", @@ -1255,6 +1264,11 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "node-watch": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/node-watch/-/node-watch-0.4.1.tgz", + "integrity": "sha1-0JR9VKmV+RE120BWtocixtfDIq0=" + }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", diff --git a/package.json b/package.json index 4ee270c1d..672fa93b0 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "debug": "~2.6.9", "express": "~4.16.0", "hbs": "~4.0.1", + "hbs-utils": "0.0.4", "http-errors": "~1.6.2", "morgan": "~1.9.0", "spotify-web-api-js": "^1.2.0", diff --git a/public/images/concert.jpg b/public/images/concert.jpg new file mode 100644 index 000000000..9ae7df7f5 Binary files /dev/null and b/public/images/concert.jpg differ diff --git a/public/images/default-artist.jpg b/public/images/default-artist.jpg new file mode 100644 index 000000000..6b2e165e7 Binary files /dev/null and b/public/images/default-artist.jpg differ diff --git a/public/images/music.png b/public/images/music.png new file mode 100644 index 000000000..301bef2a6 Binary files /dev/null and b/public/images/music.png differ diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index d0c398ec3..6217599b0 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -1,90 +1,218 @@ -:root{ - --primary-color: #1DB954; - --white: #ffffff; - --black: #222326; +html, body { + margin: 0; + padding: 0; } - html { - box-sizing: border-box; +body { + font-family: Helvetica, Arial, sans-serif; + font-size: 16px; + background-color: #66a8a6; + background: url(/images/concert.jpg) no-repeat center center fixed; } - *, *:before, *:after { - box-sizing: inherit; +.index-box { + display: flex; + flex-direction: column-reverse; + justify-content: space-between; + background-color: #fff; + border-radius: 15px; + width: 80%; + max-width: 800px; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + border: 0px; + box-shadow: 0px 1px 5px .1px rgba(0, 0, 0, 0.4); } - body { - font-family: "Lucida Grande", Helvetica, Arial, sans-serif; - color: var(--black); - font-size: 16px; - line-height: 147%; - margin: 0; - padding: 0; +@media only screen and (min-width: 900px) { + .index-box { + flex-direction: row; + max-height: 50%; + max-width: 50%; + } + + .tracks { + max-width: 60%; + } + + .hero-image { + max-height: 100%; + width: auto; + } } - h1{ - color: var(--primary-color); - text-align: center; +.a-panel { + display: flex; + flex-direction: column; + justify-content: flex-end; + padding: 1.5rem; } - a { - color: var(--primary-color); +.title > h1 { + font-size: 2.5rem; + color: #478b8a; } - ul, li{ - margin: 0; - padding: 0; +.search-bar { + display: flex; + flex-wrap: nowrap; + align-items: center; } - li{ - list-style: none; +.search-input { + width: 100%; + box-sizing: border-box; + padding: 12px 0px 12px 12px; } - .text-truncate { - max-width: 300px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; +.search-input::placeholder { + color: gray; } - section, article { - margin-bottom: 20px; - padding-top: 20px; +.search-btn { + background-color: #66a8a6; + color: #fff; + padding: 11px; + border: 0px; + border-top-right-radius: 30px; + border-bottom-right-radius: 30px; + font-size: 1.2rem; } - .container { - margin: 0 auto; - max-width: 1024px; +.search-btn:hover, .albums-btn:hover { + cursor: pointer; + background-color: #478b8a; } - .search-box{ +.albums-btn { display: flex; - justify-content: center; + justify-content: space-between; + align-items: center; + background-color: #66a8a6; + color: #fff; + padding: 11px; + border: 0px; + border-radius: 30px; + font-size: 1.2rem; + max-width: 150px; + box-shadow: 0px 1px 5px .1px rgba(0, 0, 0, 0.4); } - .search-input{ - margin-right: 2rem; -} - .btn{ - color: var(--white); - background: var(--primary-color); - padding: 0.75rem 3rem; - font-weight: 700; - letter-spacing: 2px; - text-transform: uppercase; - border: 1px solid transparent; - border-radius: 500px; - cursor: pointer; +.hero-image { + max-width: 100%; + height: auto; + border-radius: 15px; } - .cards{ +.cards { display: flex; - flex-direction: row; flex-wrap: wrap; + justify-content: space-around; +} + +.card { + display: flex; + flex-wrap: none; + flex-direction: column-reverse; + justify-content: space-between; + background-color: #fff; + border-radius: 15px; + width: 80%; + margin: 20px; + padding: 1rem; + border: 0px; + box-shadow: 0px 1px 5px .1px rgba(0, 0, 0, 0.4); + max-width: 400px; +} + +.card-title { + font-size: 1.2rem; + font-weight: bolder; + color: hsl(202, 57%, 15%); +} + +.card-image { + width: 100%; + height: auto; + box-shadow: 0px 1px 5px .1px rgba(0, 0, 0, 0.4); + border-radius: 5px; +} + +.likes { + color: hsl(201, 23%, 34%); +} + +.followers { + color: hsl(202, 57%, 15%); + font-weight: bolder; + font-size: 1.1rem; +} + +.genres { + text-transform: capitalize; + color: hsl(201, 23%, 34%); } - .card{ +.tracks { display: flex; flex-direction: column; - padding: 1rem 0.75rem; + justify-content: space-between; + margin: 30px; + + width: 90%; + padding: 1rem; +} + +.track { + display: flex; + justify-content: space-between; + align-items: center; + margin: 1px; + padding: 5px; + background-color: #fff; + box-shadow: 0px 1px 5px .1px rgba(0, 0, 0, 0.4); +} + +.nav-bar { + display: flex; + color: #fff; + padding: 6px; + height: 100px; +} + +.logo > a { + text-decoration: none; + color: #fff; + padding-left: 30px; + font-size: 2rem; +} + +.track-player { + padding-left: 10px; +} + +.track-name { + padding-left: 15px; +} + +.container { + display: flex; + justify-content: center; +} + +.title-row { + display: flex; + flex-direction: row; + justify-content: space-around; + margin: 1px; + padding: 15px; + background-color: #66a8a6; + color: #fff; + font-weight: bolder; + border-top-left-radius: 10px; + border-top-right-radius: 10px; + box-shadow: 0px 1px 5px .1px rgba(0, 0, 0, 0.4); } \ No newline at end of file diff --git a/routes/index.js b/routes/index.js index 01aa75e43..628258ad6 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,8 +1,58 @@ const express = require('express'); +const spotifyApi = require('../spotifyApi'); // Require SpotifyApi const router = express.Router(); - router.get('/', (req, res, next) =>{ - res.render('index', {title: 'Express'}); +// Index view +router.get('/', (req, res, next) =>{ + res.render('index', {title: 'Spotify'}); +}); + +// Artists view +router.get('/artists', (req, res) => { + let {artist} = req.query; + spotifyApi.searchArtists(artist) + .then(data => { + let {items} = data.body.artists; + res.render('artists', { + items, + title: 'Artists' + }); + }) + .catch(error => { + console.log(`An error ocurred: ${error}`); + }) +}); + +// Albums view +router.get('/albums/:artistId', (req, res) => { + let {artistId} = req.params; + spotifyApi.getArtistAlbums(artistId) + .then(data => { + let {items} = data.body; + res.render('albums', { + items, + title: 'Albums' + }); + }) + .catch(error => { + console.log(`An error occurred: ${error}`); + }) +}); + +// Tracks View +router.get('/tracks/:albumId', (req, res) => { + let {albumId} = req.params; + spotifyApi.getAlbumTracks(albumId) + .then(data => { + let {items} = data.body; + res.render('tracks', { + items, + title: 'Tracks' + }); + }) + .catch(error => { + console.log(`An error occurred: ${error}`); + }) }); module.exports = router; \ No newline at end of file diff --git a/spotifyApi.js b/spotifyApi.js new file mode 100644 index 000000000..16eca9fec --- /dev/null +++ b/spotifyApi.js @@ -0,0 +1,20 @@ +// Load SpotifyApi +const SpotifyWebApi = require('spotify-web-api-node'); +const clientId = '272ee2be75504c7ea7e76fb1e582a6c8'; +const clientSecret = '6d2878a851f3439b8042d0169c4dfa4e'; + +const spotifyApi = new SpotifyWebApi({ + clientId: clientId, + clientSecret: clientSecret +}); + +// Retrieve an access token +spotifyApi.clientCredentialsGrant() + .then(data => { + spotifyApi.setAccessToken(data.body['access_token']); + }) + .catch(error => { + console.log('Something went wrong when retrieving an access token', error); + }) + +module.exports = spotifyApi; \ No newline at end of file diff --git a/views/albums.hbs b/views/albums.hbs new file mode 100644 index 000000000..c05deceb9 --- /dev/null +++ b/views/albums.hbs @@ -0,0 +1,5 @@ +
+ {{#each items}} + {{> albumPartial}} + {{/each}} +
\ No newline at end of file diff --git a/views/artists.hbs b/views/artists.hbs new file mode 100644 index 000000000..4173adfe4 --- /dev/null +++ b/views/artists.hbs @@ -0,0 +1,5 @@ +
+ {{#each items}} + {{> artistPartial}} + {{/each}} +
\ No newline at end of file diff --git a/views/index.hbs b/views/index.hbs index 632605f03..adfec8b68 100644 --- a/views/index.hbs +++ b/views/index.hbs @@ -1 +1,18 @@ -

Welcome to {{title}}

\ No newline at end of file +
+
+
+

music.

+
+ +
+
+ Music icons +
+
\ No newline at end of file diff --git a/views/layout.hbs b/views/layout.hbs index 4315f2c3b..845433198 100644 --- a/views/layout.hbs +++ b/views/layout.hbs @@ -1,12 +1,19 @@ - - - {{title}} - - - -
- {{{body}}} -
- + + + + + + {{title}} + + + + + +
+ {{{body}}} +
+ \ No newline at end of file diff --git a/views/partials/albumPartial.hbs b/views/partials/albumPartial.hbs new file mode 100644 index 000000000..b3603aac5 --- /dev/null +++ b/views/partials/albumPartial.hbs @@ -0,0 +1,16 @@ +
+
+
+

{{name}}

+
+
View Tracks
+
+
+ {{#if images}} + Cover of {{name}} + {{else}} + Placeholder album picture + {{/if}} +
+
\ No newline at end of file diff --git a/views/partials/artistPartial.hbs b/views/partials/artistPartial.hbs new file mode 100644 index 000000000..f1e8a0cf9 --- /dev/null +++ b/views/partials/artistPartial.hbs @@ -0,0 +1,23 @@ +
+
+
+

{{name}}

+
+ + +
View Albums
+
+
+ {{#if images}} + Picture of {{name}} + {{else}} + Placeholder artist picture + {{/if}} +
+
diff --git a/views/tracks.hbs b/views/tracks.hbs new file mode 100644 index 000000000..25dfa6264 --- /dev/null +++ b/views/tracks.hbs @@ -0,0 +1,19 @@ +
+
+
+ Track +
+
+ Controls +
+
+ {{#each items}} +
+
{{this.name}}
+
+ +
+
+ {{/each}} +