mirror of
				https://github.com/KevinMidboe/planetposen.git
				synced 2025-10-29 17:50:32 +00:00 
			
		
		
		
	Merge pull request #12 from KevinMidboe/feat/product-page
Feat/product page
This commit is contained in:
		| @@ -74,14 +74,14 @@ export default { | |||||||
|       showMenu: false, |       showMenu: false, | ||||||
|  |  | ||||||
|       leftNavItems: [{ |       leftNavItems: [{ | ||||||
|         name: 'Program', |         name: 'Gaveposer', | ||||||
|         link: '/', |         link: '/shop' | ||||||
|       },{ |       },{ | ||||||
|         name: 'Om oss', |         name: 'Om oss', | ||||||
|         link: '/about' |         link: '/about' | ||||||
|       },{ |       },{ | ||||||
|         name: 'Gaveposer', |         name: 'Kontakt oss', | ||||||
|         link: '/shop' |         link: '/contact' | ||||||
|       }], |       }], | ||||||
|       rightNavItems: [{ |       rightNavItems: [{ | ||||||
|         name: 'Admin', |         name: 'Admin', | ||||||
| @@ -108,7 +108,7 @@ export default { | |||||||
|   methods: { |   methods: { | ||||||
|     knownLightBackgroundPage() { |     knownLightBackgroundPage() { | ||||||
|       const location = window.location.href.split('#')[1] |       const location = window.location.href.split('#')[1] | ||||||
|       return location == '/';  |       return location == '/' || location == '/contact' || location.includes('/shop/'); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										140
									
								
								frontend/components/Product.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								frontend/components/Product.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,140 @@ | |||||||
|  | <template> | ||||||
|  |   <main role="main" v-if="product"> | ||||||
|  |     <div class="banner padding-bottom--lg" :class="'bg-' + color"> | ||||||
|  |       <div class="banner-content top-show-sm col-wrap max-width"> | ||||||
|  |         <h1>{{ product.name }}</h1> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |  | ||||||
|  |     <section class="product padding-top--lg"> | ||||||
|  |       <div class="max-width col-wrap"> | ||||||
|  |         <div class="col-6 image-preview"> | ||||||
|  |           <img :src="product.image" /> | ||||||
|  |         </div> | ||||||
|  |         <div class="col-6 details"> | ||||||
|  |           <div class="pricing"> | ||||||
|  |             <span class="amount">24.99</span> | ||||||
|  |             <span class="currency">kr</span> | ||||||
|  |           </div> | ||||||
|  |  | ||||||
|  |           <p v-if="product.description">{{ product.description }}</p> | ||||||
|  |           <p v-else class="description">Notatbøkene fra All Pine Press er trykket i Oslo og papir fra Hellefoss Paper AS i Hokksund. (Notatbøkene er uten linjer)</p> | ||||||
|  |  | ||||||
|  |           <div class="stock" v-if="inStock"> | ||||||
|  |             <i class="icon icon--checkmark-circle"></i> | ||||||
|  |             På lager | ||||||
|  |           </div> | ||||||
|  |           <div class="stock soldOut" v-else> | ||||||
|  |             <i class="icon icon--close-circle"></i> | ||||||
|  |             Utsolgt | ||||||
|  |           </div> | ||||||
|  |  | ||||||
|  |           <div class="actions"> | ||||||
|  |           <!-- Variation picker --> | ||||||
|  |  | ||||||
|  |           <!-- Amount picker --> | ||||||
|  |           <!-- Buy button --> | ||||||
|  |             <Button color="black" :small="true" @click="addToCart" :scaleRotate="true">Add to cart</Button> | ||||||
|  |           </div> | ||||||
|  |  | ||||||
|  |           <div class="meta"> | ||||||
|  |             <span class="categories"> | ||||||
|  |               Kategorier: | ||||||
|  |             </span> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </section> | ||||||
|  |   </main> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script> | ||||||
|  | import Button from '@/components/ui/Button'; | ||||||
|  | import store from '@/store' | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |   components: { Button }, | ||||||
|  |   data() { | ||||||
|  |     return { | ||||||
|  |       color: Math.random() > 0.5 ? 'yellow' : 'blue', | ||||||
|  |       inStock: Math.random() > 0.5, | ||||||
|  |       product: null | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   created() { | ||||||
|  |     document.querySelector('body').style.backgroundColor = 'white'; | ||||||
|  |  | ||||||
|  |     const productSlug = this.$route.params.slug | ||||||
|  |  | ||||||
|  |     fetch(`http://localhost:30010/api/product/${ productSlug }`) | ||||||
|  |       .then(resp => resp.json()) | ||||||
|  |       .then(product => this.product = product); | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     addToCart() { | ||||||
|  |       store.dispatch('cartModule/addItemToCart', { ...this.product }); | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   beforeDestroy() { | ||||||
|  |     document.querySelector('body').style.backgroundColor = '#19171A'; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style lang="scss" scoped> | ||||||
|  | @import '/frontend//styles/spacing'; | ||||||
|  | @import '/frontend//styles/variables'; | ||||||
|  |  | ||||||
|  | main { | ||||||
|  |   color: var(--color-background); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.product { | ||||||
|  |   overflow: auto; | ||||||
|  |  | ||||||
|  |   @include mobile { | ||||||
|  |     .col-6 { | ||||||
|  |       width: 100%; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | h1 { | ||||||
|  |   text-align: center; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .image-preview img { | ||||||
|  |   border-radius: 6px; | ||||||
|  |   width: 100%; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .details { | ||||||
|  |   margin-top: 1rem; | ||||||
|  |   font-size: 1.3rem; | ||||||
|  |  | ||||||
|  |   > div, > p { | ||||||
|  |     margin-bottom: 2.25rem; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .stock { | ||||||
|  |     padding: 1rem; | ||||||
|  |     border: 4px solid var(--color-green); | ||||||
|  |     width: max-content; | ||||||
|  |  | ||||||
|  |     .icon { | ||||||
|  |       position: relative; | ||||||
|  |       top: 2px; | ||||||
|  |       left: -4px; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     &.soldOut { | ||||||
|  |       border-color: var(--color-pink); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .pricing { | ||||||
|  |     font-size: 2rem; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | </style> | ||||||
| @@ -22,6 +22,7 @@ | |||||||
|  |  | ||||||
|           <div class="content-bottom-actions"> |           <div class="content-bottom-actions"> | ||||||
|             <Button :small="true" :color="color" @click="addItemToCart">Add to cart</Button> |             <Button :small="true" :color="color" @click="addItemToCart">Add to cart</Button> | ||||||
|  |             <Button :small="true" color="yellow" @click="viewProduct">View<i class="icon icon--arrow-forward"></i></Button> | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
| @@ -61,13 +62,16 @@ export default { | |||||||
|   methods: { |   methods: { | ||||||
|     addItemToCart() { |     addItemToCart() { | ||||||
|       store.dispatch('cartModule/addItemToCart', { ...this.product }); |       store.dispatch('cartModule/addItemToCart', { ...this.product }); | ||||||
|  |     }, | ||||||
|  |     viewProduct() { | ||||||
|  |       this.$router.push('/shop/' + this.product.urlSlug) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <style lang="scss" scoped> | <style lang="scss" scoped> | ||||||
| @import '../../styles/variables'; | @import '/frontend/styles/variables'; | ||||||
|  |  | ||||||
| .product-card { | .product-card { | ||||||
|   position: relative; |   position: relative; | ||||||
| @@ -117,6 +121,8 @@ export default { | |||||||
|  |  | ||||||
|     &-actions { |     &-actions { | ||||||
|       border-top: 1px solid rgba(0,0,0,0.1); |       border-top: 1px solid rgba(0,0,0,0.1); | ||||||
|  |       display: flex; | ||||||
|  |       justify-content: space-between; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -19,6 +19,11 @@ let routes = [ | |||||||
|     path: '/about', |     path: '/about', | ||||||
|     component: (resolve) => require(['./components/About.vue'], resolve) |     component: (resolve) => require(['./components/About.vue'], resolve) | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     name: 'Shop product view', | ||||||
|  |     path: '/shop/:slug', | ||||||
|  |     component: (resolve) => require(['./components/Product.vue'], resolve) | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     name: 'Shop', |     name: 'Shop', | ||||||
|     path: '/shop', |     path: '/shop', | ||||||
|   | |||||||
| @@ -21,7 +21,7 @@ app.use(express.json()); | |||||||
| // app.use(express.urlencoded()); | // app.use(express.urlencoded()); | ||||||
|  |  | ||||||
| router.get('/products', productsController.allProducts) | router.get('/products', productsController.allProducts) | ||||||
| router.get('/product/:id', productsController.productById) | router.get('/product/:slug', productsController.productBySlug) | ||||||
| router.post('/product', productsController.addNewProduct) | router.post('/product', productsController.addNewProduct) | ||||||
|  |  | ||||||
| router.post('/variation/:id', variationsController.addNewVariationToProduct); | router.post('/variation/:id', variationsController.addNewVariationToProduct); | ||||||
|   | |||||||
| @@ -24,17 +24,17 @@ const allProducts = (req, res) => { | |||||||
|     .then(products => res.json(products)) |     .then(products => res.json(products)) | ||||||
| } | } | ||||||
|  |  | ||||||
| const productById = (req, res) => { | const productBySlug = (req, res) => { | ||||||
|   const { id } = req.params; |   const { slug } = req.params; | ||||||
|  |  | ||||||
|   if (id != null) { |   if (slug != null) { | ||||||
|     return products.getById(id) |     return products.getBySlug(slug) | ||||||
|       .then(product => handleReturnProduct(product, res)) |       .then(product => res.json({ ...product._doc })) | ||||||
|       .catch(err => handleError(err, res)) |       .catch(err => handleError(err, res)) | ||||||
|   } else { |   } else { | ||||||
|     return res.status(422).send({ |     return res.status(422).send({ | ||||||
|       success: true, |       success: true, | ||||||
|       message: 'Id must be number. Invalid request.' |       message: 'Product slug name must be included. Invalid request.' | ||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @@ -48,6 +48,6 @@ const addNewProduct = (req, res) => { | |||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|   allProducts, |   allProducts, | ||||||
|   productById, |   productBySlug, | ||||||
|   addNewProduct |   addNewProduct | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -29,8 +29,8 @@ class Products { | |||||||
|     return Product.find().populate('variations'); |     return Product.find().populate('variations'); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   getById(id) { |   getBySlug(slug) { | ||||||
|     return Product.findById(id).populate('variations'); |     return Product.findOne({ urlSlug: slug }).populate('variations'); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user