Skip to content

This is the third project I completed as a student in General Assembly's Immersive Software Engineering program. This was a group project building a full-stack MERN application.

Notifications You must be signed in to change notification settings

timJFrame/sei-project-3

Repository files navigation

SEI-Project Three: Deverr

by Alberto Cerrone, Sandra Spighel & Tim Frame AKA SpicyKiwiPizza img

You can find a live version of the app here: Deverr

Project Brief

This was the third project I completed in General Assembly's Immersive Software Engineering program. Project three was a group project. In project three we were tasked with building a full-stack MERN application over a 10 day time period.

Technologies used

Languages

  • HTML
  • CSS
  • Javascript

Frameworks & Libraries

Dependencies & Components


Contributors


The App: Deverr

App Overview

Deverr allows Developers to connect with companies or people advertsing jobs. On Deverr the people and companies advertsing jobs are referred to as Auctioners and the developers are referred to as Bidders. Inspired by the Fiverr website, the platform is based on a Bidding system where Auctioneers post Jobs and interested Developers can 'bid' to win the contract.


How to use App

  1. When a user first lands on the site they are taken to the home page. At this point that can choose from one of four options. If don't have an account they can click the register button to create an account. If they have an account they can click the login button. Alternatively they can click the jobs button and they taken to a page where they can view all jobs currently on the site. Lastly they can click the people button and will be taken to a page where they can see all the current users of the site.
  2. If a user has chosen to create an account they are taken to the register page. Firstly they need to select if they are a, ‘Bidder’ or an ‘Auctioneer. If a user chooses the ‘Bidder’, option they are required to provide name, email, password, password confirmation, bio, city, skills(can choose multiple), select there availability and choose a profile image. If a user chooses the ‘Auctioneer’ option they are required to provide a name, username, password, password confirmation, bio, city, confirm they are an auctioneer and a profile photo. If a user incorrectly fills in any form field and tries to submit the form they are will receive an error message underneath the form field that is incorrect.
  3. If the user registration is successful they are pushed onto the login page were the user needs to provide the email and password they provided during the registration process. If either field is incorrect on submission the user will receive an error message.
  4. Once logged in a user is taken to there profile page. On a users profile page they can see all the details they have provided during registration. A user also has the option to edit or delete their profile using the buttons at the bottom of the page. If a user is an auctioner they have an addtional button that gives them the option to create a new job.
  5. Bidder Profile:

    Auctioneer Profile

  6. If an ‘Auctioneer’ chooses to create a job they are moved to the create new job page. Here the user needs to provide a job title, job description, job deadline, job category, the maximum fee payable for the job(bidders need to bid under this amount) and an image.
  7. Once the job has been submitted the user is taken to the live view of the job. This is the view that all other users of the website will have if they clicked on a job thumbnail. Apart from the edit and delete button. This is only visible to the user who created the job.
  8. If a 'Bidder' clicks the jobs button located in the navigation bar they are taken to the jobs index. Here a bidder can view all jobs currently on the site or filter jobs by catergoty using the carousel at the top of the page.
  9. A ‘Bidder’ can click a job thumbnail and is taken to the live job view. Here a ‘Bidder’ can comment on a job and ask an ‘Auctioneer’ a question about a job by using the add comment form. The ‘Bidder can also place a bid on a job by using the 'Place Bid', field where the ‘Bidder’ needs to provide a short message and underneath that how much they would like to bid. Once a ‘Bidder’ has placed a bid they will get an alert to say their bid has been placed. The bid itself is only visible to the ‘Auctioneer’ who created the job.
  10. Once an ‘Auctioneer’ has received bids on a job they can select the winning bid by going to the live job view of the job they have posted. At the bottom of the job an ‘Auctioneer’ can see any bids they have received and needs to click the, ‘Accept This Bid’ button to choose the winning bid. Once the bid has been accepted, the ‘Accept This Bid’ button is removed from all remaining bids and a message is sent to the ‘Bidders’ profile with the winning bid.
  11. A ‘Bidder’ is then able to see the message when they log into their profile. From here the bidder can reply to the message and exchange details with the ‘Auctioneer’

Creating The App

Once we had a clear idea for the project and how the app could potentially work we moved on to the planning phase. We began by creating wire frames to get a feel for the flow of the site and created a Trello board to track how we were building the site and who was responsible for what. It was at this point that each team member took ownership of an specfic area: Alberto was responsible for styling, I was responsible for the front-end and Sandra was responsible for the back-end.

In the planning phase we had worked out that we would need to create 40 ‘Auctioneer Profiles’ and 60 ‘Bidder’ profiles. Alberto had found a React dependency called ‘Faker.js’ that could create dummy users. Alberto took charge of creating the dummy users and incorporating ‘Faker.js’ into the data seeding process.

We decided on having 6 types of job categories Android Developer, Apple Developer, Back-end Developer, Front-end Developer, Game Developer and UI Developer. For each category we would create 10 jobs. 60 jobs in total. Sandra started working on creating the job seeds and I moved on to creating the shell for the back-end of the site.

The code snippet below is an example of a job seed.

{
  jobTitle: 'UI Designer required for 3 month project',
  jobDescription: 'Looking for an experienced Product designer with a passion for creating intuitive and delightful user experiences. Opportunity to join a fast-growing and highly visible team. 1+ years of design experience with a strong portfolio showcasing complex platforms turned into engaging user interfaces',
  jobDeadline: '90 days',
  jobPhoto: 'https://cdn.wccftech.com/wp-content/uploads/2016/10/Front-End-Development-Bundle.jpg',
  jobCategory: 'UI-Dev',
  jobFee: 6000,
  jobIsLive: true,
}

I started off by creating a new package.json file, touching a new ‘index.js’ file and installing the dependencies I would need to get the project started that included nodemon, express and mongoose. I then added a script to the ‘package.json’ to start nodemon for hot reloading. Then I moved on to creating the ‘startServer’ function in ‘index.js. The ‘startServer’ function tested if the server was running on port 4000 and was connecting to the database. At this point I passed on creating the back-end to Sandra and began working on the remaining job seeds.

The code snippet below is the start sever function

  try {
    await mongoose.connect(dbURI, { useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true })
    console.log('🤖 Database has connected')
    app.listen(port, () => console.log(`🤖 Up and running on port ${port}`))
  } catch (err) {
    console.log('🤖 Something went wrong starting the App')
    console.log(err)
  }
}

Sandra started creating the models for the user and the jobs. Then moved onto to the controllers and router. Testing each each controller as it was created using Insomnia to make API requests. Once it was established the API requests were working correctly Sandra implemented a secure route that would check if a user was logged into the website before giving them access to creating a job. With the secure route in place, a custom error handler was implemented to handle different types of errors the back-end might encounter while a user was a making a request.

The code snippet below is the user model.

const userSchema = new mongoose.Schema({
  name: { type: String, required: true, unique: true, maxlength: 40 },
  email: { type: String, required: true, unique: true },
  password: { type: String, required: true },
  bio: { type: String, required: true },
  photo: { type: String, required: true },
  city: { type: String },
  isAuctioneer: { type: Boolean, default: false }, // * default to false
  bidderCategories: [{ type: String }], 
  bidderIsAvailable: { type: Boolean, default: false }, // * default to false
  favouritedBy: [favouritedBySchema], // Embedded relationship
  message: [messageSchema],
})

The image below are routes Sandra made in Insomnia to test routes.

While Sandra was working on the back-end Alberto started to work on the, 'seedDatabase' function that would be used to add all the dummy data to the site and drop all the current data and refresh it with new data.

The code snippet below is the 'seedDatabase' function.

sync function seedDatabase() {
  try {

    // Connect to db
    await connectToDatabase()
    console.log('🤖 Database Connected')
    
    await mongoose.connection.db.dropDatabase()
    console.log('🤖 Database dropped')

    // CREATING AUCTIONEERS DB
    const auctioneers = auctioneersSeed()
    const createdAuctioneers = await User.create(auctioneers) // ! then pass that users array
    console.log(`😎 Created ${createdAuctioneers.length} Auctioneers`)

    // CREATING BIDDERS DB
    const bidders = biddersSeed()
    const createdBidders = await User.create(bidders) // ! then pass that users array
    console.log(`😎 Created ${createdBidders.length} Bidders`)

    // MAP THROUGH JOBS DB, FOR EACH JOB ASSIGN A KEY NAMED JOB OWNER REFERENCING AUCTIONEERS DB
    const jobDataWithOwners = jobsData.map(job => {
      // Creating a random index number var
      const randomIndexNumber = Math.round(Math.random() * (createdAuctioneers.length - 1))
      // Assign each job to a random auctioneer
      job.jobOwner = createdAuctioneers[randomIndexNumber]._id
      return job
    })

    // CREATE JOBS
    const jobs = await Job.create(jobDataWithOwners)
    console.log(`POW! Fresh Database containing ${jobs.length} jobs`)
  
    await mongoose.connection.close()
    console.log('🤖 Goodbye')
    
  } catch (err) {
    console.log('😞 Something went wrong')
    console.log(err)
    
    await mongoose.connection.close()
    console.log('👋🏼 Goodbye')
  }
}

With Sandra working on the backend. I moved onto starting the front end and Alberto began styling the site.

I started off by creating a basic navigation, home, register and login component that only returned the name of each page. From there I imported those components into 'App.js' and imported 'react-router-dom' and began creating the routing for the website. Then moved onto the 'Nav' component and set-up the navigation bar. With the 'Nav' component working correctly I moved onto filling the 'Home' component with placeholder data that Alberto would change as he worked his way through styling the site.

My next task was to create the register page to capture a users information and send a post request to the database. I created a new folder called lib and in the lib folder a file called 'api.js' that would store all the API requests made to the server and could be imported to other files as needed. We used 'Axios' to make all of our requests. Next I made another new file called, 'utils' and inside utils made a custom hook that would handle capturing users input and setting it into state. It also dealt with catching an error if a user had incorrecly filled in a form field.

The code snippet below is the custom hook used in login and register page.

function useform(intialState) {
  const [formdata, setFormdata] = React.useState(intialState)
  const [errors, setErrors] = React.useState(intialState)


  const handleChange = (e) => {
    const value = e.target.type === 'checkbox' ? e.target.checked :
      e.target.value
    const nextState = { ...formdata, [e.target.name]: value }
    const nextErrorState = { ... errors, [e.target.name]: '' }
    setFormdata(nextState)
    setErrors(nextErrorState)
  }
	
  return {
    formdata,
    errors,
    handleChange,
    setFormdata,
    setErrors
  }
}

From here I moved to the, ‘Login’ component. To log a user in I created a new file inside the ‘lib’ folder called ‘auth.js’, ‘auth.js’ held all the functions that were used to authenticate a user. Inside ‘auth.js’ I added a ‘setToken’ and ‘getToken function’, the ‘setToken’ function stored a users authentication token into local storage. I then added a logout function to the ‘Nav’ component. Creating a new function in ‘auth.js’ that removes a users token from local storage. Then a user is pushed back to the home page. From here I added functionality that would hide the login and register button and show the log out button if a user is logged in and vice versa if a user was logged out.

The code snippet below are the functions from the, 'auth.js' file to get, set and remove a users token.

/*Gets a users token 
export function setToken(token) {
  window.localStorage.setItem('token', token)
}

//*Retrives a token from storage
export function getToken() {
  return window.localStorage.getItem('token')
}

//*Log a user out
export function logout() {
  window.localStorage.removeItem('token')
}

Moving on to creating the ‘UserShow’ component that would show the profile of the user who is currently logged in. This was only visible to that user. Creating a new GET request that used a users token to validate they were the current user. At this point Sandra had completed the back end and moved onto helping in the front-end working on the home page and creating the ‘UserIndex’ component that showed all the current users of the site. Alberto continued to work on the styling, he would a style page after I had added all the content to it.

Next was the, ‘JobIndex’ component that made a get request to the server to get all the jobs currently on the site. The data received back from the database was mapped over and each job was passed into its own component. Once all the data was displaying correctly I moved onto creating the ‘JobShow’ component that would show a detailed break down of a job when it was clicked by a user. This involved capturing the id of a job when it was clicked and passing the id in a post request to database and retrieving the information of that job.

The code snippet below is from the 'JobNew' component and shows the function that captures the job information from a user and the post request that is sent to database.

function JobNew() {
  const history = useHistory()
  const { formdata, handleChange, errors, setErrors } = useForm({
    jobTitle: '',
    jobDescription: '',
    jobDeadline: '',
    jobPhoto: '',
    jobCategory: '',
    jobIsLive: 'true',
    jobFee: ''
  })


  const handleSubmit = async (e) => {
    e.preventDefault()
    try {
      const { data } = await createJob(formdata)
      history.push(`/jobs/${data._id}`)
    } catch (err) {
      setErrors(err.response.data)
      console.log(err.response.data)
    }
  }

At this point the main pages of the site had been created. From here I went back through and added editing functionality to user profiles and jobs. They both used a get request to get the information of that user or job from the database that was in turn used to pre-populate a form for a user to edit. Taking the changes made by a user and making a PUT request to save the changes.

The code snippet below is from the 'JobEdit' component and shows the GET request to pre-populate a from, the function to capture the changes to a job and the PUT request to update the changes.

function EditJob() {
  const { id } = useParams()
  const history = useHistory()
  const { formdata, errors, handleChange, setFormdata, setErrors } = useForm({
    jobTitle: '',
    jobDescription: '',
    jobDeadline: '',
    jobPhoto: '',
    jobCategory: '',
    jobIsLive: 'true',
    jobFee: ''
  })


  React.useEffect(() => {
    const getData = async () => {
      const { data } = await getSingleJob(id)
      setFormdata(data)
    }
    getData()
  }, [id, setFormdata])


  const handleSubmit = async e => {
    e.preventDefault()
    try {
      await editJob(id, formdata)
      history.push(`/jobs/${id}`)
    } catch (err) {
      setErrors(err.response.data.errors)
      console.log(errors)
    }
  }

Lastly adding additional functionality to the ‘JobShow’ component this involved adding the functionality to allow a bidder to comment on a job using a put request. Then the bidding functionality that allowed a, 'Bidder' to place a bid via another put request on a job. After that is was the accepting a bid functionality that sent a PUT request to a job and changed the status of a job to false. It also sent a POST request to a, 'Bidder' that alerted the 'Bidder' that there bid had been accepted.

Challenges

This was the first group project I did while at General Assembly. This brought about new challenges that I had not encountered before. The most prevalent being all working on one Github repository at the same time and avoiding merge conflicts, but we soon overcame this by using a Trello Board to track what each team member was working on and also having clear communication in the team throughout the whole project.

The biggest challenge we ran into was the model for the user profile. Initially we had decided to have two different models, a model for the ‘Bidder’ and a model for the ‘Auctioneer’. We thought this approach would give more clarity and separation between the two types of users and the ‘Bidder’ model required additional information. Once we began building we found this wan’t the most practical approach as we would need to repeat the same code twice for each user in multiple areas of this site. To solve the problem we decided to use one user model and make the additional fields required for the ‘Bidder’ profile not automatically required on the back-end. Then on the font end we would use conditional statements to hide and reveal what each user could see dependent on there profile type.

Wins

I think the biggest win as a team was firstly how well we worked together throughout the project. We were able to communicate clearly resulting in each team member having a voice in the decision making process. Secondly we could help each other out when we ran into blockers and have group coding sessions to get past it.

Personally the biggest win for me was was working out how I could notify a ‘Bidder’ that they had the winning bid after it was chosen by the ‘Auctioneer’. As I was working on the ‘handleAcceptingBid’ function I realised that I had access to the id of the person who had created the bid. With the id I would be able to add an additional field to the user model called ‘Message’.

The code snippet below is the 'handleAcceptingBid' function from the, 'jobShow' component

  //*Handles accepting bid
  const handleAcceptingBid = async (bidId, bidOwnerId) => {
    try {
      await editJob(id, { jobIsLive: false })
      await editBid(id, bidId, { status: 'accepted' })
      await messageUser(bidOwnerId, { text: 'your bid has been accpeted' })
      const { data } = await getSingleJob(id)
      setNoBids(true)
      setJob(data)
    } catch (err){
      console.log(err)
    }
  }

Then add a new controller to ‘user.js’ controller file. That would push a new message onto the ‘message’ array of the ‘Bidder’ with the winning bid. From there I displayed the message on the profile of the winning ‘Bidder’. Where they would be guaranteed to see the message because a user would always land on there user profile first after logging in.

Learnings

  • The importance of clearly thinking database models through. Not just the information they will contain but how that information will effect other areas of the site.
  • Clear communication when working as a team is essential.

Future Features

If I had more time I would like to:

  • Change the messaging function so that it is viewable from any page on the website and make it more seamless.
  • Make the site more mobile responsive.
  • Add notification animations.

License & copyright

This project was build for educational purposes. All the information on the website, including profiles and job adds, is fictitional.

©️ Alberto Cerrone ©️ Sandra Spighel ©️ Tim Frame

About

This is the third project I completed as a student in General Assembly's Immersive Software Engineering program. This was a group project building a full-stack MERN application.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •  

Languages