diff --git a/ReadMe.md b/ReadMe.md index 1e01124..afd030f 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1,21 +1,27 @@ # YelpCamp United States -
This is my first full stack CRUD project that allows users to view, post, edit, delete, and leave reviews on the campgrounds.

Features that I added/will be added on top of Colt Steele's Web Development Bootcamp:
1.**[NPS API](https://www.nps.gov/subjects/developer/guides.htm)** - I integrated real campgrounds in the United States using an API.
+<<<<<<< HEAD +1. **☀️light and 🌙dark mode** - I used JS DOM Manipulation, localStorage, and Cookies to implement a theme feature
+2. **🌳My campgrounds** - This feature allows users to show the campgrounds they uploaded to YelpCamp!
+3. **📄pagination feature** - I also used DOM Manipulation, cookies, and Mongoose to implement pagination. This augments the performance by loading only few datas the the user wants rather than loading a whole single resource.
+4. 🔍**Search Feature** ➡️ Coming soon! + +======= 2. **☀️light and 🌙dark mode** - I used JS DOM Manipulation, localStorage, and Cookies to implement this themed feature.
3. **🌳My campgrounds** - This feature allows users to show the campgrounds they uploaded to YelpCamp!
4. **📄pagination feature** - I also used DOM Manipulation, cookies, and Mongoose to implement pagination. This augments the performance by loading only few datas the user wants rather than loading a whole single resource.
5. 🔍**Filter Campgrounds Feature** - allows users to look for campgrounds located on a certain state. ➡️ Coming soon! +>>>>>>> develop-branch -
## 🔨Stacks YelpCamp is built with **MEN** (*Mongo, Express, and Node*) stack. -
+ ## 👀 Previews #### Login @@ -57,7 +63,7 @@ YelpCamp is built with **MEN** (*Mongo, Express, and Node*) stack. #### Reviews Home page -
+ ## 🧰Tools @@ -69,7 +75,7 @@ YelpCamp is built with **MEN** (*Mongo, Express, and Node*) stack. 5. #### Embedded Javascript 6. #### Axios 7. #### Joi -
+ ## 💻 To run on your local machine: ### Prerequisties: diff --git a/controllers/campgrounds.js b/controllers/campgrounds.js index 30735ea..be54d66 100644 --- a/controllers/campgrounds.js +++ b/controllers/campgrounds.js @@ -3,47 +3,99 @@ const User = require("../models/user") const { cloudinary } = require("../cloudinary") const mbxGeocoding = require("@mapbox/mapbox-sdk/services/geocoding"); const mapBoxToken = process.env.MAPBOX_TOKEN; +const axios = require("axios"); const geocoder = mbxGeocoding({ accessToken: mapBoxToken }); -mbxGeocoding({ accessToken: mapBoxToken }); -//TODO: MAKE A MIDDLEWARE FOR RENDERING INDEX +const {config, reverseGeo} = require("../tools/index"); +/* +** TODO:IMPROVE ACCESSIBLITY +*/ module.exports.index = async (req, res) => { const result = {}; + result.results = []; const allCampgrounds = await Campground.find({}); result.allItemsFetched = allCampgrounds.map( camp => camp).length; const max = Math.ceil(result.allItemsFetched / 20.0); - let {page, limit} = req.query; + let {page, limit, q} = req.query; page = parseInt(page); - limit = parseInt(limit); - if(!page || page < 0){ - page=1;//very first page - } - if (page > max){ - page=max; - } - if(!limit){ - limit=20; - } - const startIndex = (page - 1) * limit; - const endIndex = page * limit; - const campgrounds = await Campground.find().limit(limit).skip(startIndex); - //get info for the pagination(prev and next) - if(startIndex > 0){ - result.previous = { - page: page - 1, - limit + limit = parseInt(limit); + if(!page || page < 0){ + page=1;//very first page } - } - if(endIndex < result.allItemsFetched){ - result.next = { - page: page + 1, - limit + if (page > max){ + page=max; + } + if(!limit){ + limit=20; + } + const startIndex = (page - 1) * limit; + const endIndex = page * limit; + //get info for the pagination(prev and next) + if(startIndex > 0){ + result.previous = { + page: page - 1, + limit + } + } + if(endIndex < result.allItemsFetched){ + result.next = { + page: page + 1, + limit + } } + res.cookie('currentPage', page); + + if(!q){ + //if there is no filter passed just render all campgrounds + res.clearCookie('filter'); + const campgrounds = await Campground.find().limit(limit).skip(startIndex); + result.results = campgrounds; + return res.render('campgrounds/index', {result}); + } + + //user filtered campgrounds + const queried = await axios.get(`https://developer.nps.gov/api/v1/campgrounds?limit=50&stateCode=${q}`, config); + let matchedCampground; + result.filter = q; + if(queried.data.data.length) { + //If found: save to database or just render if it already exists + const campPromises = queried.data.data.map(async function(camp) { + //make a more narrow filter for matching + if(camp.images[0]){ + matchedCampground = await Campground.find({$and:[{title: camp.name},{description: camp.description}]}); + //make a new campground + if(matchedCampground.length){ + result.results.push(...matchedCampground); + } else { + const campground = new Campground({ + location: camp.addresses[0] ? + `${camp.addresses[0].line1} ${camp.addresses[0].city} ${camp.addresses[0].stateCode}`: + await reverseGeo([Number.parseFloat( camp.longitude, 10), Number.parseFloat( camp.latitude, 10)]), + + title: camp.name, + + description: camp.description, + //assign no price if there is no cost + price: camp.fees[0] ? camp.fees[0].cost : 0, + + images: camp.images.map(c => ({ url: c.url})), + + geometry: { + type: 'Point', + coordinates: [ + camp.longitude, + camp.latitude + ] + } + }) + await campground.save(); + result.results.push(campground); + } + } + }); + await Promise.all(campPromises); } - res.cookie('currentPage', page); - result.results = campgrounds; - //for determining max number of pages - res.render('campgrounds/index', {result}); // res.send(result); + res.render('campgrounds/index', {result}) } module.exports.renderNewForm = (req, res) => { @@ -155,17 +207,17 @@ module.exports.editCampground = async (req, res) => { const {id} = req.params; //make this shorter later const campground = await Campground.findByIdAndUpdate(id, {...req.body.campground});//spread each properties - const imgs = req.files.map(f => ({ url: f.path, filename: f.filename })) - campground.images.push(...imgs) + const imgs = req.files.map(f => ({ url: f.path, filename: f.filename })); + campground.images.push(...imgs); await campground.save(); if(req.body.deleteImages){ for(let filename of req.body.deleteImages){ - await cloudinary.uploader.destroy(filename) + await cloudinary.uploader.destroy(filename); } - await campground.updateOne({$pull: {images: {filename: {$in: req.body.deleteImages}}}}) + await campground.updateOne({$pull: {images: {filename: {$in: req.body.deleteImages}}}}); } - req.flash('success', 'Successfully updated campground!') - res.redirect(`/campgrounds/${campground._id}`) + req.flash('success', 'Successfully updated campground!'); + res.redirect(`/campgrounds/${campground._id}`); } module.exports.deleteCampground = async (req, res) => { diff --git a/models/campground.js b/models/campground.js index c764d13..0d5b31a 100644 --- a/models/campground.js +++ b/models/campground.js @@ -12,11 +12,9 @@ const ImageSchema = new Schema({ ImageSchema.virtual('thumbnail').get(function () { return this.url.replace('/upload', '/upload/w_200') }) -ImageSchema.virtual('indexSize').get(function () { - return this.url.replace('/upload', '/upload/w_415,h_280') -}) + ImageSchema.virtual('showSize').get(function () { - return this.url.replace('/upload', '/upload/h_470,w_600') + return this.url.replace('/upload', '/upload/h_480') }) const opts = { diff --git a/public/javascripts/pagination.js b/public/javascripts/pagination.js index 0019bcb..59e25e4 100644 --- a/public/javascripts/pagination.js +++ b/public/javascripts/pagination.js @@ -4,12 +4,10 @@ const nextBtn = document.querySelector('.page-next'); const previousContainer = document.querySelector('.prev-btn'); const nextContainer = document.querySelector('.next-btn'); let max = Math.ceil(resultLength / 20.0); -console.log("max page:", max, " length result:", resultLength) //update total later according to data + const pageNumber = (total, max, current) => { const half = Math.round(max / 2); - console.log("total:", total) - console.log("half:", half, " current:", current) let to = max; if(current + half >= total){ @@ -19,7 +17,6 @@ const pageNumber = (total, max, current) => { } let from = to - max; - console.log("from: ", from, " to: ", to) if(current <= 1){ previousContainer.classList.add('disabled'); @@ -27,20 +24,14 @@ const pageNumber = (total, max, current) => { } else { previousContainer.removeAttribute('tabindex'); previousContainer.classList.remove('disabled'); - // prevBtn.classList.remove('text-muted'); - } - // alert(current); - // alert(total); + if(current === total){ - // nextBtn.classList.add('text-muted'); - // nextBtn.removeAttribute('href'); nextContainer.classList.add('disabled') } else { nextContainer.classList.remove('disabled'); } - return Array.from({length: max}, (_, i) => (i + 1) + from) } @@ -50,32 +41,29 @@ initialize();//initialize everything for new load function initialize (){ let arrayofBtns = pageNumber(max, 5, readCookie()); - console.log(arrayofBtns) generateButtons(pageButton, arrayofBtns); } function readCookie() { - var allcookies = document.cookie; + let allcookies = document.cookie; let key, value; // Get all the cookies pairs in an array cookiearray = allcookies.split(';'); - // Now take key value pair out of this array - for(var i=0; i { } } -const reverseGeo = async (coordinates) => { - try { - const geoData = await geocoder.reverseGeocode({ - query: coordinates, - limit: 1 - }).send() - - if(geoData.body.features[0]){ - return geoData.body.features[0].text; - } else{ - return 'NO LOCATION' - } - } catch (error) { - console.log("ERROR!:", error) - } -} - async function upload(images, camp){ for(let i =0; i< images.length; i++){ try { diff --git a/tools/index.js b/tools/index.js new file mode 100644 index 0000000..c422070 --- /dev/null +++ b/tools/index.js @@ -0,0 +1,28 @@ +const mbxGeocoding = require("@mapbox/mapbox-sdk/services/geocoding"); +const mapBoxToken = process.env.MAPBOX_TOKEN; +const key = process.env.API_KEY; +const geocoder = mbxGeocoding({ accessToken: mapBoxToken }); + +module.exports.config = { + params: + { + api_key : key + } +}; + +module.exports.reverseGeo = async (coordinates) => { + try { + const geoData = await geocoder.reverseGeocode({ + query: coordinates, + limit: 1 + }).send() + + if(geoData.body.features[0]){ + return geoData.body.features[0].text; + } else{ + return 'NO LOCATION' + } + } catch (error) { + console.log("ERROR!:", error) + } +} diff --git a/views/campgrounds/index.ejs b/views/campgrounds/index.ejs index 88b93ef..e353c40 100644 --- a/views/campgrounds/index.ejs +++ b/views/campgrounds/index.ejs @@ -1,44 +1,101 @@ <% layout('layouts/boilerplate') %>
-

All Campgrounds

+<% if(result.results.length && !result.filter) {%> +

All Campgrounds

+ +<% } else if(result.filter && result.results.length) {%> +

Showing campgrounds at

+<% } %> - - +
+ +
- Add Campground + Add a Campground
- +<% if(result.results.length) {%> <% for(let campground of result.results) {%>
-
+
<% if(campground.images.length) {%> - + <% } else {%> <% } %>
- -
+
<%= campground.title %>
<% if(campground.description.length >= 200){ %> -

<%= campground.description.substring(0, 220) %>...

+

<%= campground.description.substring(0, 190) %>...

<% } else {%>

<%= campground.description %>

<% } %> @@ -50,13 +107,21 @@
<% } %> - +<% } else {%> +

Sorry, no campgrounds to show in

+<% } %> + - \ No newline at end of file + <% if(!result.filter) {%> + + <% } else {%> + + <% } %> \ No newline at end of file diff --git a/views/campgrounds/show.ejs b/views/campgrounds/show.ejs index ec43136..5b1ce70 100644 --- a/views/campgrounds/show.ejs +++ b/views/campgrounds/show.ejs @@ -33,11 +33,13 @@
  • <%= campground.location %>
  • -
  • Submitted by <%= campground.author.username %>
  • + <% if(campground.author) {%> +
  • Submitted by <%= campground.author.username %>
  • + <% } %>
  • $<%= campground.price %>/night
- <% if(currentUser && campground.author.equals(currentUser._id)){ %> + <% if(currentUser && campground.author && campground.author.equals(currentUser._id)){ %>
Edit