Re-wrote to support my api for requesting new movies.
							
								
								
									
										14
									
								
								dist/build.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
							
								
								
									
										1
									
								
								dist/build.js.map
									
									
									
									
										vendored
									
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								dist/no-image.png
									
									
									
									
										vendored
									
									
								
							
							
						
						| Before Width: | Height: | Size: 6.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								dist/placeholder.png
									
									
									
									
										vendored
									
									
								
							
							
						
						| Before Width: | Height: | Size: 2.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								dist/pulp-fiction.jpg
									
									
									
									
										vendored
									
									
								
							
							
						
						| Before Width: | Height: | Size: 279 KiB | 
							
								
								
									
										26
									
								
								index.html
									
									
									
									
									
								
							
							
						
						| @@ -5,11 +5,12 @@ | ||||
|   "author": "Dmytro Barylo", | ||||
|   "private": true, | ||||
|   "scripts": { | ||||
|     "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot", | ||||
|     "dev": "cross-env NODE_ENV=development webpack-dev-server --hot", | ||||
|     "build": "cross-env NODE_ENV=production webpack --progress --hide-modules", | ||||
|     "start": "node server.js" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "ag-grid-vue": "^17.0.0", | ||||
|     "axios": "^0.15.3", | ||||
|     "connect-history-api-fallback": "^1.3.0", | ||||
|     "debounce": "^1.0.0", | ||||
| @@ -17,6 +18,7 @@ | ||||
|     "numeral": "^2.0.4", | ||||
|     "vue": "^2.1.0", | ||||
|     "vue-axios": "^1.2.2", | ||||
|     "vue-data-tablee": "^0.12.1", | ||||
|     "vue-router": "^2.2.1" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|   | ||||
							
								
								
									
										25
									
								
								src/App.vue
									
									
									
									
									
								
							
							
						
						| @@ -3,19 +3,20 @@ | ||||
|     <navigation></navigation> | ||||
|     <header class="header"> | ||||
|       <div class="header__search"> | ||||
|         <input class="header__search-input" type="text" v-model.trim="searchQuery" @keyup.enter="search" @blur="search" placeholder="Search for a movie..."> | ||||
|         <input class="header__search-input" type="text" v-model.trim="searchQuery" @keyup.enter="search" @blur="search" placeholder="Search for a movie or show..."> | ||||
|         <svg class="header__search-icon"> | ||||
|           <use xlink:href="#iconSearch"></use> | ||||
|         </svg> | ||||
|       </div> | ||||
|     </header> | ||||
|  | ||||
|       <movie-popup v-if="moviePopupIsVisible" @close="closeMoviePopup" :id="moviePopupId"></movie-popup> | ||||
|       <movie-popup v-if="moviePopupIsVisible" @close="closeMoviePopup" :id="moviePopupId" :type="moviePopupType"></movie-popup> | ||||
|  | ||||
|     <section class="main"> | ||||
|       <transition name="fade" @after-leave="afterLeave"> | ||||
|         <router-view name="list-router-view" :type="'page'" :mode="'collection'" :key="$route.params.category"></router-view> | ||||
|         <router-view name="search-router-view" :type="'page'" :mode="'search'" :key="$route.params.query"></router-view> | ||||
|         <router-view name="user-requests-router-view" :type="'page'" :mode="'user-requests'"></router-view> | ||||
|         <router-view name="page-router-view"></router-view> | ||||
|       </transition> | ||||
|     </section> | ||||
| @@ -36,6 +37,7 @@ export default { | ||||
|       moviePopupIsVisible: false, | ||||
|       moviePopupHistoryVisible: false, | ||||
|       moviePopupId: 0, | ||||
|       moviePopupType: 'movie', | ||||
|       searchQuery: '' | ||||
|     } | ||||
|   }, | ||||
| @@ -58,17 +60,20 @@ export default { | ||||
|       }.bind(this)); | ||||
|     }, | ||||
|     setUserStatus(){ | ||||
|       storage.sessionId = localStorage.getItem('session_id') || null; | ||||
|       storage.userId = localStorage.getItem('user_id') || null; | ||||
|       storage.token = localStorage.getItem('token') || null; | ||||
|       storage.username = localStorage.getItem('username') || null; | ||||
|       storage.admin = localStorage.getItem('admin') || null; | ||||
|     }, | ||||
|     // Movie Popup Methods | ||||
|     openMoviePopup(id, newMoviePopup){ | ||||
|     openMoviePopup(id, type, newMoviePopup){ | ||||
|       console.log('app openMoviePopup:', type) | ||||
|       if(newMoviePopup){ | ||||
|         storage.backTitle = document.title; | ||||
|       } | ||||
|       storage.createMoviePopup = newMoviePopup; | ||||
|       this.moviePopupIsVisible = true; | ||||
|       this.moviePopupId = id; | ||||
|       this.moviePopupType = type; | ||||
|       document.querySelector('body').classList.add('hidden'); | ||||
|     }, | ||||
|     closeMoviePopup(){ | ||||
| @@ -232,8 +237,8 @@ img{ | ||||
|       } | ||||
|     } | ||||
|     &-icon{ | ||||
|       width: 14px; | ||||
|       height: 14px; | ||||
|       width: 19px; | ||||
|       height: 19px; | ||||
|       fill: rgba($c-dark, 0.5); | ||||
|       transition: fill 0.5s ease; | ||||
|       pointer-events: none; | ||||
| @@ -288,7 +293,11 @@ img{ | ||||
|     font-size: 12px; | ||||
|     padding: 6px 20px 5px 20px; | ||||
|   } | ||||
|   body:not(.touch) &:hover{ | ||||
|   &:active, &:hover{ | ||||
|     background: $c-dark; | ||||
|     color: $c-white; | ||||
|   } | ||||
|   body:not(.touch) &:hover, &:focus{ | ||||
|     background: $c-dark; | ||||
|     color: $c-white; | ||||
|   } | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								src/assets/arrival.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.0 MiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/disaster-artist.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 139 KiB | 
| Before Width: | Height: | Size: 279 KiB After Width: | Height: | Size: 275 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/star-wars.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 423 KiB | 
| @@ -39,12 +39,19 @@ export default { | ||||
|     height: 100%; | ||||
|     background: rgba($c-light, 0.7); | ||||
|   } | ||||
|   &-shortList{ | ||||
|     width: 100%; | ||||
|   } | ||||
|   &__content{ | ||||
|     width: 100%; | ||||
|     padding: 0 20px; | ||||
|     text-align: center; | ||||
|     @include tablet-min{ | ||||
|       padding: 0 40px 40px 0; | ||||
|       padding: 20px 0 0 0; | ||||
|     } | ||||
|     &-shortList { | ||||
|       width: 100%; | ||||
|  | ||||
|     } | ||||
|   } | ||||
|     &__title{ | ||||
|   | ||||
| @@ -1,18 +1,12 @@ | ||||
| <template> | ||||
|   <section class="home"> | ||||
|     <header class="home__header"> | ||||
|     <header class="home__header" v-bind:style="{ 'background-image': 'url(' + imageFile + ')' }"> | ||||
|       <div class="home__header-wrap"> | ||||
|         <h1 class="home__header-title">The Movie DB App</h1> | ||||
|         <h1 class="home__header-title">Request new movies or tv shows for plex</h1> | ||||
|         <strong class="home__header-subtitle">Made with Vue.js</strong> | ||||
|         <a href="https://github.com/dmtrbrl/tmdb-app" target="_blank" class="home__header-link"> | ||||
|           <svg class="home__header-link-icon"> | ||||
|             <use xlink:href="#iconGithub"></use> | ||||
|           </svg> | ||||
|           <span>View Code</span> | ||||
|         </a> | ||||
|       </div> | ||||
|     </header> | ||||
|     <movies-list v-for="item in listTypes" v-if="item.isCategory" :type="'component'" :mode="'collection'" :category="item.query" :shortList="true"></movies-list> | ||||
|     <movies-list v-for="item in listTypes" v-if="item.isCategory" :type="'component'" :mode="item.type" :category="item.query" :shortList="true"></movies-list> | ||||
|   </section> | ||||
| </template> | ||||
|  | ||||
| @@ -25,7 +19,8 @@ export default { | ||||
|   components: { MoviesList }, | ||||
|   data(){ | ||||
|     return { | ||||
|       listTypes: storage.listTypes | ||||
|       listTypes: storage.listTypes, | ||||
|       imageFile: 'dist/pulp-fiction.jpg' | ||||
|     } | ||||
|   }, | ||||
|   created(){ | ||||
| @@ -50,9 +45,9 @@ export default { | ||||
|     background-position: 50% 50%; | ||||
|     position: relative; | ||||
|     background-color: $c-dark; | ||||
|     background-image: url('~assets/pulp-fiction.jpg'); | ||||
|     background-image: url('~assets/arrival.jpg'); | ||||
|     @include tablet-min{ | ||||
|       height: 384px; | ||||
|       height: 284px; | ||||
|     } | ||||
|     &:before{ | ||||
|       content: ""; | ||||
|   | ||||
| @@ -10,44 +10,96 @@ | ||||
|           <div class="movie__title"> | ||||
|             <h1 class="movie__title-text"> | ||||
|               {{ movie.title }} | ||||
|               <span v-if="movie.tagline">{{ movie.tagline }}</span> | ||||
|               <!-- <span>{{ movie.type }}</span> --> | ||||
|             </h1> | ||||
|             <span> | ||||
|             </span> | ||||
|           </div> | ||||
|         </div> | ||||
|       </header> | ||||
|       <div class="movie__main"> | ||||
|         <div class="movie__wrap movie__wrap--main" :class="{'movie__wrap--page': type=='page'}"> | ||||
|           <div class="movie__actions" v-if="userLoggedIn && favoriteChecked"> | ||||
|             <a href="#" class="movie__actions-link" :class="{'active' : favorite === true}" @click.prevent="toggleFavorite"> | ||||
|               <svg class="movie__actions-icon" :class="{'waiting' : favorite === ''}"> | ||||
|                 <use xlink:href="#iconFavorite"></use> | ||||
|           <!-- <div class="movie__actions" v-if="userLoggedIn && favoriteChecked"> --> | ||||
|           <div class="movie__actions"> | ||||
|  | ||||
|             <a class="movie__actions-link" v-if="matched" :class="{'active' : matched}"> | ||||
|               <svg class="movie__actions-icon"> | ||||
|                 <use xlink:href="#iconExsits"></use> | ||||
|               </svg> | ||||
|               <span class="movie__actions-text" v-if="favorite === ''">Wait...</span> | ||||
|               <span class="movie__actions-text" v-else-if="favorite">Marked as Favorite</span> | ||||
|               <span class="movie__actions-text" v-else>Mark as Favorite?</span> | ||||
|               <span class="movie__actions-text"> Already in plex  🎉</span> | ||||
|             </a> | ||||
|             <a class="movie__actions-link" v-else="matched"> | ||||
|               <svg class="movie__actions-icon"> | ||||
|                 <use xlink:href="#iconNot_exsits"></use> | ||||
|               </svg> | ||||
|               <span class="movie__actions-text"> Not in plex yet</span> | ||||
|             </a> | ||||
|  | ||||
|             <a class="movie__actions-link" :class="{'active' : requested}" v-if="this.requested"> | ||||
|               <svg class="movie__actions-icon"> | ||||
|                 <use xlink:href="#iconSent"></use> | ||||
|               </svg> | ||||
|               <span class="movie__actions-text"> Requested to be downloaded</span> | ||||
|             </a> | ||||
|             <a class="movie__actions-link" v-else="this.requested"  @click.prevent="sendRequest"> | ||||
|               <svg class="movie__actions-icon" :class="{'waiting' : requested}"> | ||||
|                 <use xlink:href="#iconUnmatched"></use> | ||||
|               </svg> | ||||
|               <span class="movie__actions-text"> Request to be downloaded?</span> | ||||
|             </a> | ||||
|  | ||||
|             <a class="movie__actions-link" @click="showTorrents=true" v-if="admin==='true'" :class="{'active' : showTorrents}"> | ||||
|               <svg class="movie__actions-icon"> | ||||
|                 <use xlink:href="#icon_torrents"></use> | ||||
|               </svg> | ||||
|               <span class="movie__actions-text"> Search for torrents</span> | ||||
|             </a> | ||||
|  | ||||
|             <a class="movie__actions-link" @click.prevent="openTmdb"> | ||||
|               <svg class="movie__actions-icon"> | ||||
|                 <use xlink:href="#icon_info"></use> | ||||
|               </svg> | ||||
|               <span class="movie__actions-text"> See more info</span> | ||||
|             </a> | ||||
|           </div> | ||||
|           <div class="movie__info"> | ||||
|             <div v-if="movie.overview" class="movie__description"> | ||||
|               {{ movie.overview }} | ||||
|             <div v-if="movie.summary" class="movie__description"> | ||||
|               {{ movie.summary }} | ||||
|             </div> | ||||
|             <div class="movie__details"> | ||||
|               <div v-if="movie.genres.length" class="movie__details-block"> | ||||
|              <!--  <div v-if="movie.genres.length" class="movie__details-block"> | ||||
|                 <h2 class="movie__details-title"> | ||||
|                   Genres | ||||
|                 </h2> | ||||
|                 <div class="movie__details-text"> | ||||
|                   {{ nestedDataToString(movie.genres) }} | ||||
|                 </div> | ||||
|               </div> | ||||
|               <div v-if="movie.release_date" class="movie__details-block"> | ||||
|               </div> --> | ||||
|               <div v-if="movie.year" class="movie__details-block"> | ||||
|                 <h2 class="movie__details-title"> | ||||
|                   Release Date | ||||
|                 </h2> | ||||
|                 <div class="movie__details-text" v-formatDate="movie.release_date"></div> | ||||
|                 <div class="movie__details-text"> | ||||
|                   {{ movie.year }} | ||||
|                 </div> | ||||
|               </div> | ||||
|               <div v-if="movie.type == 'show'" class="movie__details-block"> | ||||
|                 <h2 class="movie__details-title"> | ||||
|                   Seasons | ||||
|                 </h2> | ||||
|                 <div class="movie__details-text"> | ||||
|                   10 | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|  | ||||
|           <!-- <TableDemo class="movie__admin">This is it</TableDemo> --> | ||||
|          | ||||
|           <div class="movie__admin" v-if="admin == 'true' && showTorrents"> | ||||
|               <h2 class="movie__admin-title">torrents: {{ movie.title }}</h2> | ||||
|               <TorrentList :query="movie.title"></TorrentList> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
| @@ -59,9 +111,11 @@ import axios from 'axios' | ||||
| import storage from '../storage.js' | ||||
| import img from '../directives/v-image.js' | ||||
| import formatDate from '../directives/v-formatDate.js' | ||||
| import TorrentList from './TorrentList.vue' | ||||
|  | ||||
| export default { | ||||
|   props: ['id', 'type'], | ||||
|   props: ['id', 'type', 'mediaType'], | ||||
|   components: { TorrentList }, | ||||
|   directives: { | ||||
|     img: img, | ||||
|     formatDate: formatDate | ||||
| @@ -74,30 +128,40 @@ export default { | ||||
|       movieBackdropSrc: '', | ||||
|       userLoggedIn: storage.sessionId ? true : false, | ||||
|       favoriteChecked: false, | ||||
|       favorite: '' | ||||
|       requested: false, | ||||
|       admin: localStorage.getItem('admin'), | ||||
|       showTorrents: false | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     loaded(){ | ||||
|       return this.movieLoaded ? true : false; | ||||
|     } | ||||
|   }, | ||||
|   // computed: { | ||||
|   //   loaded(){ | ||||
|   //     return this.movieLoaded ? true : false; | ||||
|   //   } | ||||
|   // }, | ||||
|   methods: { | ||||
|     fetchMovie(id){ | ||||
|       axios.get(`https://api.themoviedb.org/3/movie/${id}?api_key=${storage.apiKey}&language=en-US`) | ||||
|       this.id = id; | ||||
|       (this.mediaType == 'show') ? this.tmdbType = 'show' : this.tmdbType = 'movie' | ||||
|       // Fetch from seasoned to get matched status | ||||
|       // axios.get(`https://apollo.kevinmidboe.com/api/v1/plex/request/${id}?type=${'show'}&api_key=${storage.apiKey}&language=en-US`) | ||||
|       axios.get(`https://api.kevinmidboe.com/api/v1/plex/request/${id}?type=${this.mediaType}`) | ||||
|       // axios.get(`http://localhost:31459/api/v1/plex/request/${id}?type=${this.mediaType}&api_key=${storage.apiKey}&language=en-US`) | ||||
|       .then(function(resp){ | ||||
|           let movie = resp.data; | ||||
|           this.movie = movie; | ||||
|           this.poster(); | ||||
|           this.backdrop(); | ||||
|           this.matched = this.movie.matchedInPlex; | ||||
|           this.requested = this.movie.requested; | ||||
|           if(this.userLoggedIn){ | ||||
|             this.checkIfInFavorites(movie.id); | ||||
|             this.movieLoaded = true; | ||||
|           } else { | ||||
|             this.movieLoaded = true; | ||||
|           } | ||||
|           // Push state | ||||
|           if(storage.createMoviePopup){ | ||||
|             storage.moviePath = '/movie/' + id; | ||||
|             storage.moviePath = '/request/' + this.mediaType + '/' + id; | ||||
|             history.pushState({ popup: true }, null, storage.moviePath); | ||||
|             storage.createMoviePopup = false; | ||||
|           } | ||||
| @@ -105,17 +169,19 @@ export default { | ||||
|           document.title = this.movie.title + storage.pageTitlePostfix; | ||||
|       }.bind(this)) | ||||
|       .catch(function(error) { | ||||
|         console.log(error.response) | ||||
|         this.$router.push({ name: '404' }); | ||||
|       }.bind(this)); | ||||
|     }, | ||||
|     poster() { | ||||
|       // Change the poster resolution | ||||
|       if(this.movie.poster_path){ | ||||
|         this.moviePosterSrc = 'https://image.tmdb.org/t/p/w600_and_h900_bestv2' + this.movie.poster_path; | ||||
|         this.moviePosterSrc = 'https://image.tmdb.org/t/p/w300' + this.movie.poster_path; | ||||
|       } | ||||
|     }, | ||||
|     backdrop(){ | ||||
|       if(this.movie.backdrop_path){ | ||||
|         this.movieBackdropSrc = 'https://image.tmdb.org/t/p/w500' + this.movie.backdrop_path; | ||||
|       if(this.movie.background_path){ | ||||
|         this.movieBackdropSrc = 'https://image.tmdb.org/t/p/w500' + this.movie.background_path; | ||||
|       } | ||||
|     }, | ||||
|     nestedDataToString(data) { | ||||
| @@ -125,13 +191,15 @@ export default { | ||||
|       return resultString; | ||||
|     }, | ||||
|     checkIfInFavorites(id){ | ||||
|       axios.get(`https://api.themoviedb.org/3/movie/${id}/account_states?api_key=${storage.apiKey}&session_id=${storage.sessionId}`) | ||||
|       // Change to check in plex | ||||
|       axios.get(`https://api.themoviedb.org/3/${this.tmdbType}/${id}/account_states?api_key=${storage.apiKey}&session_id=${storage.sessionId}`) | ||||
|       .then(function(resp){ | ||||
|           this.favorite = resp.data.favorite; | ||||
|           this.favoriteChecked = true; | ||||
|           this.movieLoaded = true; | ||||
|       }.bind(this)) | ||||
|     }, | ||||
|     // Toggle the downloading status if admin | ||||
|     toggleFavorite(){ | ||||
|       let favoriteInvert = !this.favorite; | ||||
|       this.favorite = ''; | ||||
| @@ -144,7 +212,28 @@ export default { | ||||
|         this.favorite = favoriteInvert; | ||||
|         eventHub.$emit('updateFavorite'); | ||||
|       }.bind(this)); | ||||
|     } | ||||
|     }, | ||||
|     // Send a request for a specific movie | ||||
|     sendRequest(){ | ||||
|       this.requested = '' | ||||
|       axios.post(`https://api.kevinmidboe.com/api/v1/plex/request/${this.id}?type=${this.mediaType}`, | ||||
|         { headers: {authorization: storage.token} }, | ||||
|       ) | ||||
|       // axios.post(`https://api.kevinmidboe.com/api/v1/plex/request/${this.id}?api_key=${storage.apiKey}&session_id=${storage.sessionId}`, { | ||||
|       .then(function(resp){ | ||||
|         if (resp.data.success) | ||||
|           this.requested = true; | ||||
|         else | ||||
|           this.requested = false; | ||||
|       }.bind(this)); | ||||
|     }, | ||||
|     openTmdb(){ | ||||
|       window.location.replace('https://www.themoviedb.org/' + this.tmdbType + '/' + this.id) | ||||
|     }, | ||||
|     // Search torrents by query | ||||
|     // searchForTorrents() { | ||||
|     //   axios.get(`https://apollo.kevinmidboe.com/api/v1/plex/request/${id}?type=${'movie'}&api_key=${storage.apiKey}&language=en-US`) | ||||
|     // }, | ||||
|   }, | ||||
|   watch: { | ||||
|     id: function(val){ | ||||
| @@ -153,6 +242,7 @@ export default { | ||||
|   }, | ||||
|   created(){ | ||||
|     this.fetchMovie(this.id); | ||||
|     console.log('admin: ', this.admin) | ||||
|   } | ||||
| } | ||||
| </script> | ||||
| @@ -292,19 +382,26 @@ export default { | ||||
|         &.active{ | ||||
|           color: $c-dark; | ||||
|         } | ||||
|         &.pending{ | ||||
|           color: #f8bd2d; | ||||
|         } | ||||
|       } | ||||
|       &-icon{ | ||||
|         width: 16px; | ||||
|         height: 16px; | ||||
|         width: 18px; | ||||
|         height: 18px; | ||||
|         margin: 0 10px 0 0; | ||||
|         fill: rgba($c-dark, 0.5); | ||||
|         transition: fill 0.5s ease, transform 0.5s ease; | ||||
|         &.waiting{ | ||||
|           transform: scale(0.8, 0.8); | ||||
|         } | ||||
|         &.pending{ | ||||
|           fill: #f8bd2d; | ||||
|         } | ||||
|       } | ||||
|       &-link:hover &-icon{ | ||||
|         fill: rgba($c-dark, 0.75); | ||||
|         cursor: pointer; | ||||
|       } | ||||
|       &-link.active &-icon{ | ||||
|         fill: $c-green; | ||||
| @@ -312,6 +409,7 @@ export default { | ||||
|       &-text{ | ||||
|         display: block; | ||||
|         padding-top: 2px; | ||||
|         cursor: pointer; | ||||
|       } | ||||
|     } | ||||
|     &__info{ | ||||
| @@ -339,10 +437,15 @@ export default { | ||||
|         } | ||||
|       } | ||||
|       &__details{ | ||||
|         &-block{ | ||||
|           float: left; | ||||
|         } | ||||
|         &-block:not(:last-child){ | ||||
|           margin-bottom: 20px; | ||||
|           margin-right: 20px; | ||||
|           @include tablet-min{ | ||||
|             margin-bottom: 30px; | ||||
|             margin-right: 30px; | ||||
|           } | ||||
|         } | ||||
|         &-title{ | ||||
| @@ -361,5 +464,28 @@ export default { | ||||
|           margin-top: 5px; | ||||
|         } | ||||
|       } | ||||
|     &__admin{ | ||||
|       width: 100%; | ||||
|       padding: 20px; | ||||
|       order: 2; | ||||
|       @include tablet-min{ | ||||
|         order: 3; | ||||
|         padding: 40px; | ||||
|         padding-top: 0px; | ||||
|         width: 100%; | ||||
|       } | ||||
|       &-title{ | ||||
|           margin: 0; | ||||
|           font-weight: 400; | ||||
|           text-transform: uppercase; | ||||
|           text-align: center; | ||||
|           font-size: 14px; | ||||
|           color: $c-green; | ||||
|           padding-bottom: 20px; | ||||
|           @include tablet-min{ | ||||
|             font-size: 16px; | ||||
|           } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| </style> | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| <template> | ||||
|   <div class="movie-popup" @click="$emit('close')"> | ||||
|     <div class="movie-popup__box" @click.stop> | ||||
|       <movie :id="id"></movie> | ||||
|       <movie :id="id" :mediaType="type"></movie> | ||||
|       <button class="movie-popup__close" @click="$emit('close')"></button> | ||||
|     </div> | ||||
|     <i class="loader"></i> | ||||
| @@ -12,7 +12,7 @@ | ||||
| import Movie from './Movie.vue'; | ||||
|  | ||||
| export default { | ||||
|   props: ['id'], | ||||
|   props: ['id', 'type'], | ||||
|   components: { Movie }, | ||||
|   created(){ | ||||
|     window.addEventListener('keyup', function(e){ | ||||
|   | ||||
| @@ -4,7 +4,10 @@ | ||||
|       <header class="movies__header"> | ||||
|         <h2 class="movies__title">{{ listTitle }}</h2> | ||||
|         <span class="movies__results" v-if="!shortList">{{ countResults }}</span> | ||||
|         <router-link v-if="shortList" class="movies__link" :to="{name: 'home-category', params: {category: category}}"> | ||||
|         <router-link v-if="shortList && mode != 'user-requests'" class="movies__link" :to="{name: 'home-category', params: {category: category}}"> | ||||
|           View All | ||||
|         </router-link> | ||||
|          <router-link v-if="shortList && mode == 'user-requests'" class="movies__link" :to="{name: 'user-requests'}"> | ||||
|           View All | ||||
|         </router-link> | ||||
|       </header> | ||||
| @@ -16,7 +19,7 @@ | ||||
|       </div> | ||||
|     </div> | ||||
|     <i v-if="!listLoaded" class="loader"></i> | ||||
|     <section v-if="!movies.length" class="not-found"> | ||||
|     <section v-if="!movies.length && !shortList" class="not-found"> | ||||
|       <div class="not-found__content"> | ||||
|         <h2 class="not-found__title" v-if="mode == 'search'">Nothing Found</h2> | ||||
|         <h2 class="not-found__title" v-if="mode == 'favorite'">You haven't added any favorite movies</h2> | ||||
| @@ -61,13 +64,18 @@ export default { | ||||
|       return this.$route.params.query || ''; | ||||
|     }, | ||||
|     request(){ | ||||
|       console.log('todays mode is: ', this.mode); | ||||
|       if(this.mode == 'search'){ | ||||
|         return `https://api.themoviedb.org/3/search/movie?api_key=${storage.apiKey}&language=en-US&query=${this.query}&page=${this.currentPage}`; | ||||
|         return `https://api.kevinmidboe.com/api/v1/plex/request?query=${this.query}&page=${this.currentPage}`; | ||||
|       } else if(this.mode == 'requests' || this.$route.params.category == 'requests') { | ||||
|         return `https://api.kevinmidboe.com/api/v1/plex/requests/all?page=${this.currentPage}&status=requested`; | ||||
|       } else if(this.mode == 'collection') { | ||||
|         let caregory = this.$route.params.category || this.category; | ||||
|         return `https://api.themoviedb.org/3/movie/${caregory}?api_key=${storage.apiKey}&language=en-US&page=${this.currentPage}`; | ||||
|       } else if(this.mode == 'favorite') { | ||||
|         return `https://api.themoviedb.org/3/account/${storage.userId}/favorite/movies?api_key=${storage.apiKey}&session_id=${storage.sessionId}&language=en-US&sort_by=created_at.desc&page=${this.currentPage}`; | ||||
|         let category = this.$route.params.category || this.category; | ||||
|         return `https://api.kevinmidboe.com/api/v1/tmdb/list/${category}?page=${this.currentPage}`; | ||||
|       } else if(this.mode == 'history') { | ||||
|         return 'https://api.kevinmidboe.com/api/v1/user/history'; | ||||
|       } else if(this.mode == 'user-requests') { | ||||
|         return 'https://api.kevinmidboe.com/api/v1/user/requests'; | ||||
|       } | ||||
|     }, | ||||
|     countResults(){ | ||||
| @@ -80,9 +88,13 @@ export default { | ||||
|   }, | ||||
|   methods: { | ||||
|     fetchCategory(){ | ||||
|       axios.get(this.request) | ||||
|       axios.get(this.request, { | ||||
|         headers: {authorization: storage.token}, | ||||
|       }) | ||||
|       .then(function(resp){ | ||||
|           let data = resp.data; | ||||
|           console.log('data: ', data) | ||||
|  | ||||
|           if(this.shortList){ | ||||
|             this.movies = data.results.slice(0, 5); | ||||
|             this.pages = 1; | ||||
| @@ -144,11 +156,15 @@ export default { | ||||
|     if(this.mode == 'search'){ | ||||
|       this.listTitle = storage.categories['search']; | ||||
|       eventHub.$emit('setSearchQuery'); | ||||
|     } else if(this.mode == 'requests') { | ||||
|       this.listTitle = storage.categories['requests']; | ||||
|     } else if(this.mode == 'collection') { | ||||
|       let caregory = this.$route.params.category || this.category; | ||||
|       this.listTitle = storage.categories[caregory]; | ||||
|       let category = this.$route.params.category || this.category; | ||||
|       this.listTitle = storage.categories[category]; // <-- this | ||||
|     } else if(this.mode == 'favorite') { | ||||
|       this.listTitle = storage.categories['favorite']; | ||||
|     } else if(this.mode == 'user-requests') { | ||||
|       this.listTitle = storage.categories['user-requests']; | ||||
|     } | ||||
|     this.fetchCategory(); | ||||
|     eventHub.$on('updateFavorite', this.updateFavorite); | ||||
|   | ||||
| @@ -1,12 +1,13 @@ | ||||
| <template> | ||||
|   <li class="movies-item"> | ||||
|     <a class="movies-item__link" :class="{'no-image': noImage}" :href="'/movie/' + movie.id" @click.prevent="openMoviePopup(movie.id, true)"> | ||||
|     <a class="movies-item__link" :class="{'no-image': noImage}" href="" @click.prevent="openMoviePopup(movie.id, movie.type, true)"> | ||||
|       <figure class="movies-item__poster"> | ||||
|         <img v-if="!noImage" class="movies-item__img" src="~assets/placeholder.png" v-img="poster()" alt=""> | ||||
|         <img v-if="noImage" class="movies-item__img is-loaded" src="~assets/no-image.png" alt=""> | ||||
|       </figure> | ||||
|       <div class="movies-item__content"> | ||||
|         <p class="movies-item__title">{{ movie.title }}</p> | ||||
|         <p class="movies-item__title">{{ movie.year }}</p> | ||||
|       </div> | ||||
|     </a> | ||||
|   </li> | ||||
| @@ -28,13 +29,14 @@ export default { | ||||
|   methods: { | ||||
|     poster() { | ||||
|       if(this.movie.poster_path){ | ||||
|         return 'https://image.tmdb.org/t/p/w370_and_h556_bestv2' + this.movie.poster_path; | ||||
|         return 'https://image.tmdb.org/t/p/w300' + this.movie.poster_path; | ||||
|       } else { | ||||
|         this.noImage = true; | ||||
|       } | ||||
|     }, | ||||
|     openMoviePopup(id, event){ | ||||
|       eventHub.$emit('openMoviePopup', id, event); | ||||
|     openMoviePopup(id, type, event){ | ||||
|       console.log('open:', id, type, event) | ||||
|       eventHub.$emit('openMoviePopup', id, type, event); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -12,7 +12,7 @@ | ||||
|     </div> | ||||
|     <ul class="nav__list"> | ||||
|       <li class="nav__item" v-for="item in listTypes" v-if="item.isCategory"> | ||||
|         <router-link class="nav__link" :to="{name: 'home-category', params: {category: item.query}}"> | ||||
|         <router-link class="nav__link" :to="{name: item.name, params: {mode: item.type, category: item.query}}"> | ||||
|           <div class="nav__link-wrap"> | ||||
|             <svg class="nav__link-icon"> | ||||
|               <use :xlink:href="'#icon_' + item.query"></use> | ||||
| @@ -22,14 +22,22 @@ | ||||
|         </router-link> | ||||
|       </li> | ||||
|       <li class="nav__item nav__item--profile"> | ||||
|         <div  class="nav__link nav__link--profile"  @click="requestToken" v-if="!userLoggedIn"> | ||||
| <!--         <div  class="nav__link nav__link--profile"  @click="requestToken" v-if="!userLoggedIn"> | ||||
|           <div class="nav__link-wrap"> | ||||
|             <svg class="nav__link-icon"> | ||||
|               <use xlink:href="#iconLogin"></use> | ||||
|             </svg> | ||||
|             <span class="nav__link-title">Log In</span> | ||||
|           </div> | ||||
|         </div> | ||||
|         </div> --> | ||||
|          <router-link  class="nav__link nav__link--profile" :to="{name: 'signin'}" v-if="!userLoggedIn"> | ||||
|           <div class="nav__link-wrap"> | ||||
|             <svg class="nav__link-icon"> | ||||
|               <use xlink:href="#iconLogin"></use> | ||||
|             </svg> | ||||
|             <span class="nav__link-title">Sign in</span> | ||||
|           </div> | ||||
|         </router-link> | ||||
|         <router-link  class="nav__link nav__link--profile" :to="{name: 'profile'}" v-if="userLoggedIn"> | ||||
|           <div class="nav__link-wrap"> | ||||
|             <svg class="nav__link-icon"> | ||||
| @@ -50,12 +58,12 @@ export default { | ||||
|   data(){ | ||||
|     return { | ||||
|       listTypes: storage.listTypes, | ||||
|       userLoggedIn: storage.sessionId ? true : false | ||||
|       userLoggedIn: localStorage.getItem('token') ? true : false | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     setUserStatus(){ | ||||
|       this.userLoggedIn = storage.sessionId ? true : false; | ||||
|       this.userLoggedIn = localStorage.getItem('token') ? true : false; | ||||
|     }, | ||||
|     requestToken(){ | ||||
|       eventHub.$emit('requestToken'); | ||||
|   | ||||
| @@ -1,17 +1,20 @@ | ||||
| <template> | ||||
|   <section class="profile"> | ||||
|     <div class="profile__content" v-if="userLoggedIn === true"> | ||||
|     <div class="profile__content" v-if="userLoggedIn"> | ||||
|       <header class="profile__header"> | ||||
|         <h2 class="profile__title">Hello {{ userName }}</h2> | ||||
|         <h2 class="profile__title">{{ emoji }} Welcome {{ userName }}</h2> | ||||
|         <button class="button" @click="logOut">Log Out</button> | ||||
|       </header> | ||||
|       <movies-list :type="'component'" :mode="'favorite'"></movies-list> | ||||
|       <movies-list v-for="item in listTypes" v-if="item.isProfileContent" :type="'component'" :mode="item.type" :category="item.query" :shortList="true"></movies-list> | ||||
|       <!-- <movies-list v-for="item in listTypes" v-if="item.isCategory" :type="'component'" :mode="item.type" :shortList="true"></movies-list> --> | ||||
|       <!-- <created-lists></created-lists> --> | ||||
|     </div> | ||||
|     <section class="not-found" v-if="userLoggedIn === false"> | ||||
|     <section class="not-found" v-if="!userLoggedIn"> | ||||
|       <div class="not-found__content"> | ||||
|         <h2 class="not-found__title">Authentication Request Failed</h2> | ||||
|         <button class="not-found__button button" @click="requestToken">Log In</button> | ||||
|         <router-link :to="{name: 'signin'}" exact title="Sign in here"> | ||||
|           <button class="not-found__button button">Sign In</button> | ||||
|         </router-link> | ||||
|       </div> | ||||
|     </section> | ||||
|   </section> | ||||
| @@ -28,25 +31,12 @@ export default { | ||||
|   data(){ | ||||
|     return{ | ||||
|       userLoggedIn: '', | ||||
|       userName: '' | ||||
|       userName: '', | ||||
|       emoji: '', | ||||
|       listTypes: storage.listTypes | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     requestPermission(){ | ||||
|       let query = location.search.substring(1); | ||||
|       if(query.length){ | ||||
|         let params = query.split('&'); | ||||
|         let token = params[0].split('=')[1]; | ||||
|         let status = params[1].split('=')[0]; | ||||
|         if(status == 'approved'){ | ||||
|           this.createSession(token); | ||||
|         } else { | ||||
|           this.userLoggedIn = false; | ||||
|         } | ||||
|       } else { | ||||
|         this.userLoggedIn = false; | ||||
|       } | ||||
|     }, | ||||
|     createSession(token){ | ||||
|       axios.get(`https://api.themoviedb.org/3/authentication/session/new?api_key=${storage.apiKey}&request_token=${token}`) | ||||
|       .then(function(resp){ | ||||
| @@ -60,16 +50,14 @@ export default { | ||||
|           } | ||||
|       }.bind(this)); | ||||
|     }, | ||||
|     getUserInfo(){ | ||||
|       axios.get(`https://api.themoviedb.org/3/account?api_key=${storage.apiKey}&session_id=${storage.sessionId}`) | ||||
|     getNewEmoji(){ | ||||
|       axios.get(`https://api.kevinmidboe.com/api/v1/emoji`) | ||||
|       .then(function(resp){ | ||||
|           let data = resp.data; | ||||
|           this.userName = data.username; | ||||
|           if (!localStorage.getItem('user_id')) localStorage.setItem('user_id', data.id); | ||||
|           this.emoji = resp.data.emoji; | ||||
|       }.bind(this)) | ||||
|       .catch(function (error) { | ||||
|         this.logOut(); | ||||
|       }.bind(this)); | ||||
|     }, | ||||
|     getUserInfo(){ | ||||
|       this.userName = localStorage.getItem('username');  | ||||
|     }, | ||||
|     requestToken(){ | ||||
|       eventHub.$emit('requestToken'); | ||||
| @@ -83,11 +71,12 @@ export default { | ||||
|   created(){ | ||||
|     document.title = 'Profile' + storage.pageTitlePostfix; | ||||
|     storage.backTitle = document.title; | ||||
|     if(!storage.sessionId){ | ||||
|       this.requestPermission(); | ||||
|     if(!localStorage.getItem('token')){ | ||||
|       this.userLoggedIn = false; | ||||
|     } else { | ||||
|       this.userLoggedIn = true; | ||||
|       this.getUserInfo(); | ||||
|       this.getNewEmoji(); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -97,14 +86,6 @@ export default { | ||||
| @import "./src/scss/variables"; | ||||
| @import "./src/scss/media-queries"; | ||||
| .profile{ | ||||
|   &__content{ | ||||
|     .wrapper{ | ||||
|       min-height: calc(100vh - 175px); | ||||
|       @include tablet-min{ | ||||
|         min-height: calc(100vh - 171px); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   &__header{ | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|   | ||||
							
								
								
									
										267
									
								
								src/components/Register.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,267 @@ | ||||
| <template> | ||||
|   <section class="profile"> | ||||
|     <div class="profile__content"> | ||||
|       <header class="profile__header"> | ||||
|         <h2 class="profile__title">Register new user</h2> | ||||
|       </header> | ||||
|  | ||||
|       <form class="form"> | ||||
|         <div class="form__buffer"></div> | ||||
|  | ||||
|         <div> | ||||
|           <div class="form__group"> | ||||
|             <svg class="form__group__input-icon"> | ||||
|               <use xlink:href="#iconEmail"></use> | ||||
|             </svg> | ||||
|             <input class="form__group-input" type="username" ref="username" placeholder="Username" > | ||||
|           </div> | ||||
|           <div class="form__group"> | ||||
|             <svg class="form__group__input-icon"> | ||||
|               <use xlink:href="#iconKeyhole"></use> | ||||
|             </svg> | ||||
|             <input class="form__group-input" type="password" ref="password" placeholder="Password"> | ||||
|           </div> | ||||
|           <div class="form__group"> | ||||
|             <svg class="form__group__input-icon"> | ||||
|               <use xlink:href="#iconKeyhole"></use> | ||||
|             </svg> | ||||
|             <input class="form__group-input" type="password" ref="password_re" placeholder="Repeat password"> | ||||
|           </div> | ||||
|  | ||||
|           <transition name="message-fade"> | ||||
|             <div class="message" :class="messageClass" v-if="showMessage"> | ||||
|               <span class="message-text">{{ messageText }}</span> | ||||
|               <span class="message-dismiss" v-on:click="dismissMessage">X</span> | ||||
|             </div> | ||||
|           </transition> | ||||
|  | ||||
|           <div class="form__group"> | ||||
|             <button type="button" class="button" v-on:click="requestNewUser">Register</button> | ||||
|           </div> | ||||
|         </div> | ||||
|       </form> | ||||
|        | ||||
|       <div class="form__group"> | ||||
|         <router-link class="form__group-link" :to="{name: 'signin'}" exact title="Sign in here"> | ||||
|           <span class="form__group-signin">Sign in here</span> | ||||
|         </router-link> | ||||
|       </div> | ||||
|  | ||||
|       <!-- <created-lists></created-lists> --> | ||||
|     </div> | ||||
|     <section class="not-found" v-if="userLoggedIn === false"> | ||||
|       <div class="not-found__content"> | ||||
|         <h2 class="not-found__title">Authentication Request Failed</h2> | ||||
|         <button class="not-found__button button" @click="requestToken">Log In</button> | ||||
|       </div> | ||||
|     </section> | ||||
|   </section> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import axios from 'axios' | ||||
| import storage from '../storage.js' | ||||
| import MoviesList from './MoviesList.vue' | ||||
| // import CreatedLists from './CreatedLists.vue' | ||||
|  | ||||
| export default { | ||||
|   components: { MoviesList }, | ||||
|   data(){ | ||||
|     return{ | ||||
|       userLoggedIn: '', | ||||
|       userName: '', | ||||
|       showMessage: false, | ||||
|       messageClass: 'message-success', | ||||
|       messageText: 'hello world' | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     requestNewUser(){ | ||||
|       let username = this.$refs.username.value; | ||||
|       let password = this.$refs.password.value; | ||||
|       let password_re = this.$refs.password_re.value; | ||||
|  | ||||
|       let verifyCredentials = this.checkCredentials(username, password, password_re); | ||||
|        | ||||
|       if (verifyCredentials.verified) { | ||||
|         axios.post(`https://api.kevinmidboe.com/api/v1/user`, { | ||||
|           username: username, | ||||
|           password: password | ||||
|         }) | ||||
|         .then(function(resp) { | ||||
|           let data = resp.data; | ||||
|           if (data.success){ | ||||
|             this.msg(data.message, 'success'); | ||||
|             localStorage.setItem('token', data.token); | ||||
|             localStorage.setItem('username', username); | ||||
|             localStorage.setItem('admin', data.admin) | ||||
|              | ||||
|             eventHub.$emit('setUserStatus'); | ||||
|             this.$router.push({ name: 'profile' }) | ||||
|           } | ||||
|         }.bind(this)) | ||||
|         .catch(function(error){ | ||||
|           this.msg(error.response.data.error, 'warning') | ||||
|         }.bind(this)); | ||||
|       }  | ||||
|       else { | ||||
|         this.msg(verifyCredentials.reason, 'warning'); | ||||
|       } | ||||
|     }, | ||||
|     checkCredentials(username, password, password_re) { | ||||
|       if (password !== password_re) { | ||||
|         return { | ||||
|           verified: false, | ||||
|           reason: 'Passwords do not match' | ||||
|         } | ||||
|       }  | ||||
|       else if (username === undefined) { | ||||
|         return { | ||||
|           verified: false, | ||||
|           reason: 'Please insert username' | ||||
|         } | ||||
|       }  | ||||
|       else { | ||||
|         return { | ||||
|           verified: true, | ||||
|           reason: 'Verified credentials' | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     msg(text, status){ | ||||
|       if (status === 'warning') | ||||
|         this.messageClass = 'message-warning'; | ||||
|       else if (status === 'success') | ||||
|         this.messageClass = 'message-success'; | ||||
|       else | ||||
|         this.messageClass = 'message-info'; | ||||
|       this.messageText = text; | ||||
|       this.showMessage = true; | ||||
|       // setTimeout(() => this.showMessage = false, 3500); | ||||
|     }, | ||||
|     dismissMessage(){ | ||||
|       this.showMessage = false; | ||||
|     }, | ||||
|     logOut(){ | ||||
|       localStorage.clear(); | ||||
|       eventHub.$emit('setUserStatus'); | ||||
|       this.$router.push({ name: 'home' }); | ||||
|     } | ||||
|   }, | ||||
|   created(){ | ||||
|     document.title = 'Profile' + storage.pageTitlePostfix; | ||||
|     storage.backTitle = document.title; | ||||
|   }, | ||||
|   mounted(){ | ||||
|     // this.$refs.email.focus(); | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss"> | ||||
| @import "./src/scss/variables"; | ||||
| @import "./src/scss/media-queries"; | ||||
| .message-enter-active { | ||||
|   transition: all .3s ease; | ||||
| } | ||||
| .message-fade-leave-active { | ||||
|   transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0); | ||||
| } | ||||
| .message-fade-enter, .message-fade-leave-to { | ||||
|   opacity: 0; | ||||
| } | ||||
| .message{ | ||||
|   width: 75%; | ||||
|   max-width: 35rem; | ||||
|   margin: 0 auto; | ||||
|   margin-bottom: 1rem; | ||||
|   padding: 12px 15px 12px 15px; | ||||
|   position: relative; | ||||
|   &-text{ | ||||
|     font-weight: 300; | ||||
|   } | ||||
|   &-dismiss{ | ||||
|     position: absolute; | ||||
|     font-size: 17px; | ||||
|     font-weight: 100; | ||||
|     top: 0; | ||||
|     right: 0; | ||||
|     margin-top: 2px; | ||||
|     margin-right: 5px; | ||||
|     cursor: pointer; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .message-warning{ | ||||
|   background-color: #f2dede; | ||||
|   border: 1px solid #b75b91; | ||||
|   color: #b75b91; | ||||
| } | ||||
| .message-success{ | ||||
|   background-color: #dff0d9; | ||||
|   border: 1px solid #3e7549; | ||||
|   color: #3e7549; | ||||
| } | ||||
|  | ||||
| .form{ | ||||
|   z-index: 15; | ||||
|   background-color: $c-light; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   @include tablet-min{ | ||||
|   } | ||||
|   &__buffer{ | ||||
|     width: 100%; | ||||
|     height: 4rem; | ||||
|   } | ||||
|   &__group{ | ||||
|     display: flex; | ||||
|     justify-content: center; | ||||
|     @include tablet-min{ | ||||
|     } | ||||
|     &-input{ | ||||
|       width: 75%; | ||||
|       max-width: 35rem; | ||||
|       padding: 15px 10px 15px 45px; | ||||
|       outline: none; | ||||
|       background-color: $c-white; | ||||
|       color: $c-dark; | ||||
|       font-weight: 100; | ||||
|       font-size: 20px; | ||||
|       border: 1px solid $c-dark; | ||||
|       margin-bottom: 1rem; | ||||
|       margin-left: -2.2rem; | ||||
|       z-index: 3; | ||||
|       &:focus, &:hover { | ||||
|         border-color: $c-dark; | ||||
|       } | ||||
|     } | ||||
|     &-input[type="username"] { | ||||
|       margin-bottom: 3rem; | ||||
|     } | ||||
|     &__input-icon{ | ||||
|       width: 24px; | ||||
|       height: 24px; | ||||
|       fill: rgba($c-dark, 0.5); | ||||
|       transition: fill 0.5s ease; | ||||
|       pointer-events: none; | ||||
|       margin-top: 15px; | ||||
|       margin-left: 15px; | ||||
|       z-index: 8; | ||||
|     } | ||||
|     &-link{ | ||||
|       text-decoration: none; | ||||
|       color: black; | ||||
|       margin-top: 1rem; | ||||
|     } | ||||
|     &-signin{ | ||||
|       text-transform: uppercase; | ||||
|       font-weight: 300; | ||||
|       font-size: 11px; | ||||
|       line-height: 2; | ||||
|       letter-spacing: 0.5px; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| </style> | ||||
							
								
								
									
										122
									
								
								src/components/Signin.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,122 @@ | ||||
| <template> | ||||
|   <section class="profile"> | ||||
|     <div class="profile__content"> | ||||
|       <header class="profile__header"> | ||||
|         <h2 class="profile__title">Register new user</h2> | ||||
|       </header> | ||||
|  | ||||
|       <form class="form"> | ||||
|         <div class="form__buffer"></div> | ||||
|         <div> | ||||
|           <div class="form__group"> | ||||
|             <svg class="form__group__input-icon"> | ||||
|               <use xlink:href="#iconEmail"></use> | ||||
|             </svg> | ||||
|             <input class="form__group-input" type="username" ref="username" placeholder="Username" > | ||||
|           </div> | ||||
|           <div class="form__group"> | ||||
|             <svg class="form__group__input-icon"> | ||||
|               <use xlink:href="#iconKeyhole"></use> | ||||
|             </svg> | ||||
|             <input class="form__group-input" type="password" ref="password" placeholder="Password"> | ||||
|           </div> | ||||
|            | ||||
|           <transition name="message-fade"> | ||||
|               <div class="message" :class="messageClass" v-if="showMessage"> | ||||
|                 <span class="message-text">{{ messageText }}</span> | ||||
|                 <span class="message-dismiss" @click="showMessage=false">X</span> | ||||
|               </div> | ||||
|             </transition> | ||||
|            | ||||
|           <div class="form__group"> | ||||
|             <button type="button" class="button" v-on:click="signin">Sign in</button> | ||||
|           </div> | ||||
|         </div> | ||||
|       </form> | ||||
|        | ||||
|       <div class="form__group"> | ||||
|         <router-link class="form__group-link" :to="{name: 'register'}" exact title="Sign in here"> | ||||
|           <span class="form__group-signin">Don't have a user? Register here</span> | ||||
|         </router-link> | ||||
|       </div> | ||||
|  | ||||
|       <!-- <created-lists></created-lists> --> | ||||
|     </div> | ||||
|   </section> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import axios from 'axios' | ||||
| import storage from '../storage.js' | ||||
| import MoviesList from './MoviesList.vue' | ||||
| // import CreatedLists from './CreatedLists.vue' | ||||
|  | ||||
| export default { | ||||
|   components: { MoviesList }, | ||||
|   data(){ | ||||
|     return{ | ||||
|       userLoggedIn: '', | ||||
|       userName: '', | ||||
|       showMessage: false, | ||||
|       messageClass: 'message-success', | ||||
|       messageText: 'hello world' | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     signin(){ | ||||
|       let username = this.$refs.username.value; | ||||
|       let password = this.$refs.password.value; | ||||
|  | ||||
|       axios.post(`https://api.kevinmidboe.com/api/v1/user/login`, { | ||||
|         username: username, | ||||
|         password: password | ||||
|       }) | ||||
|       .then(function (resp){ | ||||
|         let data = resp.data; | ||||
|         if (data.success){ | ||||
|           localStorage.setItem('token', data.token); | ||||
|           localStorage.setItem('username', username); | ||||
|           localStorage.setItem('admin', data.admin); | ||||
|           this.userLoggedIn = true; | ||||
|            | ||||
|           eventHub.$emit('setUserStatus'); | ||||
|           this.$router.push({ name: 'profile' }) | ||||
|         } | ||||
|       }.bind(this)) | ||||
|       .catch(function (error){ | ||||
|         if (error.message.endsWith('401')) | ||||
|           this.msg('Incorrect username or password ', 'warning') | ||||
|         else | ||||
|           this.msg(error.message, 'warning') | ||||
|       }.bind(this)); | ||||
|     }, | ||||
|     msg(text, status){ | ||||
|       if (status === 'warning') | ||||
|         this.messageClass = 'message-warning'; | ||||
|       else if (status === 'success') | ||||
|         this.messageClass = 'message-success'; | ||||
|       else | ||||
|         this.messageClass = 'message-info'; | ||||
|       this.messageText = text; | ||||
|       this.showMessage = true; | ||||
|       // setTimeout(() => this.showMessage = false, 3500); | ||||
|     }, | ||||
|     toggleView(){ | ||||
|       this.register = false; | ||||
|     }, | ||||
|   }, | ||||
|   created(){ | ||||
|     document.title = 'Sign in' + storage.pageTitlePostfix; | ||||
|     storage.backTitle = document.title; | ||||
|     if (this.userLoggedIn == true) { | ||||
|       this.$router.push({ name: 'profile' }) | ||||
|     } | ||||
|   }, | ||||
|   mounted(){ | ||||
|     // this.$refs.email.focus(); | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss"> | ||||
| </style> | ||||
							
								
								
									
										148
									
								
								src/components/TorrentList.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,148 @@ | ||||
| <template> | ||||
|   <section> | ||||
|     <div v-if="listLoaded"> | ||||
|       <div v-if="torrents.length"> | ||||
|         <data-tablee | ||||
|       :rows="torrents" | ||||
|       :cols="cols" | ||||
|       empty="-" | ||||
|     > | ||||
|       <span | ||||
|         class="data-tablee-icon" | ||||
|         slot="sort-icon" | ||||
|         slot-scope="{ sortment, sorted, arrow }" | ||||
|       > | ||||
|         {{ sorted ? arrow + ' ' + (sortment === 'ascending' ? 'ASC' : 'DESC') : '' }} | ||||
|       </span> | ||||
|  | ||||
|       <template | ||||
|         slot="row" | ||||
|         slot-scope="{ row, index }" | ||||
|       > | ||||
|         <td class="data-tablee-cell -content data-tablee-text" v-bind:title="row.name" v-if="!renderName">{{ row.name.slice(0, 50) }}</td> | ||||
|         <td class="data-tablee-cell -content data-tablee-text" v-on:click="showInfo(row.name)">{{ row.seed }}</td> | ||||
|         <td class="data-tablee-cell -content data-tablee-text" v-on:click="showInfo(row.name)">{{ row.size }}</td> | ||||
|         <td class="data-tablee-cell -content data-tablee-text magnet"> | ||||
|           <button type='button' class="button" @click="sendTorrent(row.magnet)">Add</button> | ||||
|         </td> | ||||
|       </template> | ||||
|     </data-tablee> | ||||
|       </div> | ||||
|       <section v-if="!torrents.length" class=""> | ||||
|         <div class="not-found__content"> | ||||
|           <h2 class="not-found__title">{{ errorMessage }}</h2> | ||||
|         </div> | ||||
|       </section> | ||||
|     </div> | ||||
|     <i v-if="!listLoaded" class="torrentloader"></i> | ||||
|   </section> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import axios from 'axios' | ||||
| import numeral from 'numeral' | ||||
| import storage from '../storage.js' | ||||
|  | ||||
| // import testTorrents from './torrents.json'; | ||||
|  | ||||
| let tablet = window.innerWidth < 768 ? true : false; | ||||
|  | ||||
| export default { | ||||
|   props: ['query'], | ||||
|   beforeRouteLeave (to, from, next) { | ||||
|     if(from.name == 'search'){ | ||||
|       eventHub.$emit('setSearchQuery', true); | ||||
|     } | ||||
|     next(); | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       torrents: [], | ||||
|       listLoaded: false, | ||||
|       errorMessage: '', | ||||
|       renderName: tablet, | ||||
|       cols: [ | ||||
|         { label: 'Name', field: 'name', sort: true, hidden: tablet }, | ||||
|         { label: 'Seeders', field: 'seed', sort: true }, | ||||
|         { label: 'Size', field: 'size', sort: (a, b) => this.sortableSize(a) - this.sortableSize(b) }, | ||||
|         { label: 'Add', align: 'center' } | ||||
|       ], | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     fetchTorrents(){ | ||||
|       axios.get(`https://api.kevinmidboe.com/api/v1/pirate/search?query=${this.query}&filter=all&page=${this.currentPage}`, { | ||||
|         headers: {authorization: storage.token}, | ||||
|       }) | ||||
|       .then(resp => { | ||||
|           let data = resp.data; | ||||
|           this.torrents = data.results; | ||||
|           this.listLoaded = true; | ||||
|       }) | ||||
|       .catch(e => { | ||||
|         const error = e.toString() | ||||
|         this.errorMessage = error.indexOf('401') != -1 ? 'Permission denied' : 'Nothing found'; | ||||
|         this.listLoaded = true; | ||||
|       }); | ||||
|     }, | ||||
|     sendTorrent(magnet){ | ||||
|       axios.post(`https://api.kevinmidboe.com/api/v1/pirate/add`, { | ||||
|         magnet: magnet }, { headers: {authorization: storage.token} | ||||
|       }) | ||||
|       .catch((resp) => { console.log('error:', resp.data) }) | ||||
|       .then((resp) => { console.log('addTorrent resp: ', resp) }) | ||||
|     }, | ||||
|     showInfo(text){ | ||||
|       alert(text) | ||||
|     }, | ||||
|     sortableSize(string) { | ||||
|        const UNITS = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; | ||||
|        const [numStr, unit] = string.split(' '); | ||||
|        if (UNITS.indexOf(unit) === -1) | ||||
|           return string | ||||
|        const exponent = UNITS.indexOf(unit) * 3 | ||||
|        return numStr * (Math.pow(10, exponent)) | ||||
|     }, | ||||
|   }, | ||||
|   created(){ | ||||
|     this.fetchTorrents(); | ||||
|   }, | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style src="../scss/vue-data-tablee.css"></style> | ||||
| <style lang="scss" scoped> | ||||
| @import "./src/scss/variables"; | ||||
| @import "./src/scss/media-queries"; | ||||
| .magnet{ | ||||
|   text-align: center; | ||||
| } | ||||
| .add{ | ||||
|   padding: 3px 15px 3px 15px; | ||||
|   &:hover, &:active{ | ||||
|     background: $c-dark; | ||||
|     color: $c-white; | ||||
|   } | ||||
| } | ||||
| .torrentloader{ | ||||
|   animation: load 1s linear infinite; | ||||
|   border: 2px solid $c-dark; | ||||
|   border-radius: 50%; | ||||
|   display: block; | ||||
|   height: 30px; | ||||
|   left: 50%; | ||||
|   margin: 0 auto; | ||||
|   width: 30px; | ||||
|   &:after { | ||||
|     border: 5px solid $c-green; | ||||
|     border-radius: 50%; | ||||
|     content: ''; | ||||
|     left: 10px; | ||||
|     position: absolute; | ||||
|     top: 16px; | ||||
|   } | ||||
| } | ||||
| @keyframes load { | ||||
|   100% { transform: rotate(360deg); } | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										1895
									
								
								src/components/torrents.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -2,13 +2,14 @@ import Vue from 'vue' | ||||
| import VueRouter from 'vue-router' | ||||
| import axios from 'axios' | ||||
| import router from './routes' | ||||
| import DataTablee from 'vue-data-tablee' | ||||
|  | ||||
| import App from './App.vue' | ||||
|  | ||||
| window.eventHub = new Vue(); | ||||
|  | ||||
|  | ||||
| Vue.use(VueRouter, axios) | ||||
| Vue.use(DataTablee) | ||||
|  | ||||
| new Vue({ | ||||
|   el: '#app', | ||||
|   | ||||
| @@ -10,11 +10,18 @@ let routes = [ | ||||
|   }, | ||||
|   { | ||||
|     name: 'home-category', | ||||
|     path: '/movies/:category', | ||||
|     path: '/list/:category', | ||||
|     components: { | ||||
|       'list-router-view': require('./components/MoviesList.vue') | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     name: 'request', | ||||
|     path: '/request/all', | ||||
|     components: { | ||||
|       'request-router-view': require('./components/MoviesList.vue') | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     name: 'search', | ||||
|     path: '/search/:query', | ||||
| @@ -22,6 +29,13 @@ let routes = [ | ||||
|       'search-router-view': require('./components/MoviesList.vue') | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     name: 'user-requests', | ||||
|     path: '/profile/requests', | ||||
|     components: { | ||||
|       'user-requests-router-view': require('./components/MoviesList.vue') | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     name: 'movie', | ||||
|     path: '/movie/:id', | ||||
| @@ -30,12 +44,40 @@ let routes = [ | ||||
|     }, | ||||
|     beforeEnter: (to, from, next) => { | ||||
|       if(history.state && history.state.popup && from.name){ | ||||
|         eventHub.$emit('openMoviePopup', to.params.id, false); | ||||
|         eventHub.$emit('openMoviePopup', to.params.id, 'movie', false); | ||||
|         return; | ||||
|       } | ||||
|       next(); | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     name: 'show', | ||||
|     path: '/show/:id', | ||||
|     components: { | ||||
|       'page-router-view': require('./components/MoviePage.vue') | ||||
|     }, | ||||
|     beforeEnter: (to, from, next) => { | ||||
|       if(history.state && history.state.popup && from.name){ | ||||
|         eventHub.$emit('openMoviePopup', to.params.id, 'show', false); | ||||
|         return; | ||||
|       } | ||||
|       next(); | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     name: 'register', | ||||
|     path: '/register', | ||||
|     components: { | ||||
|       'search-router-view': require('./components/Register.vue') | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     name: 'signin', | ||||
|     path: '/signin', | ||||
|     components: { | ||||
|       'search-router-view': require('./components/Signin.vue') | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     name: 'profile', | ||||
|     path: '/profile', | ||||
| @@ -52,12 +94,13 @@ let routes = [ | ||||
|   }, | ||||
|   { | ||||
|     path: '*', | ||||
|     redirect: '/404' | ||||
|     redirect: '/' | ||||
|   } | ||||
| ]; | ||||
|  | ||||
| const router =  new VueRouter({ | ||||
|   mode: 'history', | ||||
|   base: '/request', | ||||
|   routes, | ||||
|   linkActiveClass: 'is-active' | ||||
| }); | ||||
|   | ||||
| @@ -3,3 +3,7 @@ $c-green: #01d277; | ||||
| $c-dark: #081c24; | ||||
| $c-white: #ffffff; | ||||
| $c-light: #f8f8f8; | ||||
| $c-green-light: #dff0d9; | ||||
| $c-green-dark: #3e7549; | ||||
| $c-red-light: #f2dede; | ||||
| $c-red-dark: #b75b91; | ||||
|   | ||||
							
								
								
									
										68
									
								
								src/scss/vue-data-tablee.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,68 @@ | ||||
| .data-tablee { | ||||
|   overflow: hidden; | ||||
|   border: 1px solid #eaedef; | ||||
|   width: 100%; | ||||
|   border-radius: 5px; | ||||
|   border-spacing: 0; } | ||||
|  | ||||
| .data-tablee-cell { | ||||
|   position: relative; | ||||
|   min-height: calc(27px + 4px); | ||||
|   padding: 10px; | ||||
|   border-top: 1px solid #eaedef; } | ||||
|   .data-tablee-row:first-child > .data-tablee-cell { | ||||
|     border-top: 0; } | ||||
|   .data-tablee-cell::before { | ||||
|     position: absolute; | ||||
|     left: 0; | ||||
|     top: 50%; | ||||
|     display: block; | ||||
|     width: 1px; | ||||
|     height: 27px; | ||||
|     background-color: #eaedef; | ||||
|     transform: translateY(-50%); | ||||
|     content: ''; } | ||||
|   .data-tablee-cell:first-child::before { | ||||
|     content: none; } | ||||
|   .data-tablee-cell.-right { | ||||
|     text-align: right; } | ||||
|   .data-tablee-cell.-left { | ||||
|     text-align: left; } | ||||
|   .data-tablee-cell.-center { | ||||
|     text-align: center; } | ||||
|   .data-tablee-cell.-clickable { | ||||
|     cursor: pointer; } | ||||
|  | ||||
| .data-tablee-text { | ||||
|   font-size: 13px; | ||||
|   font-weight: 400; | ||||
|   color: #5e6684; } | ||||
|  | ||||
| .data-tablee-cell.-header { | ||||
|   background-color: #fdfdfd; } | ||||
|   .data-tablee-cell.-header > .data-tablee-text, | ||||
|   .data-tablee-cell.-header > .data-tablee-icon { | ||||
|     display: inline-block; | ||||
|     font-size: 12px; | ||||
|     font-weight: 400; | ||||
|     text-transform: uppercase; | ||||
|     color: #bec0d3; } | ||||
|   .data-tablee-cell.-header > .data-tablee-icon { | ||||
|     opacity: 0; | ||||
|     transition: opacity .3s ease, transform .3s ease; } | ||||
|   .data-tablee-cell.-header.-sortable { | ||||
|     cursor: pointer; } | ||||
|     .data-tablee-cell.-header.-sortable > .data-tablee-icon { | ||||
|       opacity: .2; } | ||||
|     .data-tablee-cell.-header.-sortable:hover > .data-tablee-icon { | ||||
|       opacity: .8; } | ||||
|     .data-tablee-cell.-header.-sortable:active > .data-tablee-icon { | ||||
|       transition: transform .1s ease; | ||||
|       transform: scale(1.5); } | ||||
|     .data-tablee-cell.-header.-sortable.-right { | ||||
|       padding-right: 6px; } | ||||
|   .data-tablee-cell.-header.-sorting > .data-tablee-icon { | ||||
|     opacity: 1; } | ||||
|  | ||||
| .data-tablee-text { | ||||
|   line-height: 1; } | ||||
| @@ -1,34 +1,40 @@ | ||||
| let storage = { | ||||
|   apiKey: 'a70dbfe19b800809dfdd3e89e8532c9e', | ||||
|   sessionId: localStorage.getItem('session_id') || null, | ||||
|   userId: localStorage.getItem('user_id') || null, | ||||
|   token: localStorage.getItem('token') || null, | ||||
|   username: localStorage.getItem('username') || null, | ||||
|   admin: localStorage.getItem('admin') || null, | ||||
|   pageTitlePostfix: ' — ' + document.title, | ||||
|   listTypes: [ | ||||
|     { | ||||
|       title: 'Popular Movies', | ||||
|       shortTitle: 'Popular', | ||||
|       query: 'popular', | ||||
|       type: 'collection', | ||||
|       isCategory: true | ||||
|     }, | ||||
|     { | ||||
|       title: 'Top Rated Movies', | ||||
|       shortTitle: 'Top Rated', | ||||
|       query: 'top_rated', | ||||
|       type: 'collection', | ||||
|       isCategory: true | ||||
|     }, | ||||
|     { | ||||
|       title: 'Upcoming Movies', | ||||
|       shortTitle: 'Upcoming', | ||||
|       query: 'upcoming', | ||||
|       name: 'home-category', | ||||
|       type: 'collection', | ||||
|       isCategory: true | ||||
|     }, | ||||
|     { | ||||
|       title: 'Requested Movies & Shows', | ||||
|       shortTitle: 'Requested', | ||||
|       query: 'requests', | ||||
|       name: 'home-category', | ||||
|       type: 'requests', // Maybe change to separate group | ||||
|       isCategory: true, | ||||
|       isProfileContent: true | ||||
|     }, | ||||
|     { | ||||
|       title: 'Now Playing Movies', | ||||
|       shortTitle: 'Now Playing', | ||||
|       query: 'now_playing', | ||||
|       query: 'nowplaying', | ||||
|       name: 'home-category', | ||||
|       type: 'collection', | ||||
|       isCategory: true | ||||
|     }, | ||||
|     { | ||||
|       title: 'Popular Movies', | ||||
|       shortTitle: 'Popular', | ||||
|       query: 'popular', | ||||
|       name: 'home-category', | ||||
|       type: 'collection', | ||||
|       isCategory: true | ||||
|     }, | ||||
| @@ -41,6 +47,15 @@ let storage = { | ||||
|       title: 'Your Favorite Movies', | ||||
|       query: 'favorite', | ||||
|       isCategory: false | ||||
|     }, | ||||
|     { | ||||
|       title: 'Your Requests', | ||||
|       shortTitle: 'User Requests', | ||||
|       query: 'user-requests', | ||||
|       name: 'user-requests', | ||||
|       type: 'user-requests', | ||||
|       isProfileContent: true | ||||
|       // isCategory: true, | ||||
|     } | ||||
|   ], | ||||
|   categories: {}, | ||||
|   | ||||
| @@ -5,7 +5,7 @@ module.exports = { | ||||
|   entry: './src/main.js', | ||||
|   output: { | ||||
|     path: path.resolve(__dirname, './dist'), | ||||
|     publicPath: '/dist/', | ||||
|     publicPath: '/request/dist/', | ||||
|     filename: 'build.js' | ||||
|   }, | ||||
|   module: { | ||||
| @@ -29,7 +29,8 @@ module.exports = { | ||||
|         test: /\.(png|jpg|gif|svg)$/, | ||||
|         loader: 'file-loader', | ||||
|         options: { | ||||
|           name: '[name].[ext]?[hash]' | ||||
|           name: '[name].[ext]' | ||||
|           // name: '[name].[ext]?[hash]' | ||||
|         } | ||||
|       } | ||||
|     ] | ||||
| @@ -49,7 +50,7 @@ module.exports = { | ||||
|   performance: { | ||||
|     hints: false | ||||
|   }, | ||||
|   devtool: '#eval-source-map' | ||||
|   // devtool: '#eval-source-map' | ||||
| } | ||||
|  | ||||
| if (process.env.NODE_ENV === 'production') { | ||||
|   | ||||