DEV Community

trafficBSolutions
trafficBSolutions

Posted on

Creating an Apply Now Page in MERN Stack

Hi, I am having an issue with my MERN Stack. This website needs an Apply Now/Job Application page where someone can apply for a job on my website. The problem is that I need the server side to send an email to my gmail address in order to be notified that someone is wanting a job to work at the company. The Server-Side consists of a Routes, Controllers, and Models folder and contains the index.js to export the PORT Number which is 8000. Here is the main folder structure:
--client
---node_modules
---public
vite.svg
---src
---assets
---react.svg
---components
---FileInput.jsx
---Navbar.jsx
---css
---apply.css
---FileInput.css
---home.css
---pages
---applynow.jsx
---home.jsx
---index.jsx
---trafficcontrol.jsx
---App.jsx
---main.jsx
---eslintrc.cjs
---.gitignore
---agl logo.jpg
---banner.jpg
---barrels.jpg
---cartag.jpg
---citylimit.jpg
---decal.jpg
---flagger.jpg
---gp logo.png
---hoodie your design here.jpg
---index.html
---package-lock.json
---package.json
---photos.jpg
---rollup.JPG
---sec logo.png
---shirt.jpg
---sign.jpg
---stop.jpg
---tbs logo.png
---tc logo.png
---tri state logo.png
---vite.config.js
---wall.jpg
---window.jpg
---yield.jpg
---your design here shirt.jpg
---server
---controllers
---authControl.js
---duplicateHandler.js
---models
---user.js
---node_modules
---routes
---autoRoute.js
---.env
---index.js
---package-lock.json
---package.json

The Apply Now Page(../pages/applynow.jsx) consists of:

import React, { useState } from 'react';
import '../css/apply.css';
import FileInput from '../components/FileInput'
import axios from 'axios'
import {toast} from 'react-hot-toast'
import { useNavigate } from 'react-router-dom'

export default function Apply() {
  const navigate = useNavigate()
  const [data, formData] = useState({
    first: '',
    last: '',
    email: '',
    phone: '',
    resume: '',
    cover: '',
    message: ''
  });
  const [submissionMessage, setSubmissionMessage] = useState('');
  const [submissionErrorMessage, setSubmissionErrorMessage] = useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();
    const { first, last, email, phone, resume, cover, message } = data;
    try {
      const formData = new FormData();
      formData.append('first', first);
      formData.append('last', last);
      formData.append('email', email);
      formData.append('phone', phone);
      formData.append('resume', resume);
      formData.append('cover', cover);
      formData.append('message', message);

      const response = await axios.post('/applynow', formData, {
        headers: {
          'Content-Type': 'multipart/form-data'
        }
      });

      if (response.data.error) {
        setSubmissionErrorMessage(response.data.error);
      } else {
        formData({
          first: '',
          last: '',
          email: '',
          resume: '',
          cover: '',
          phone: '',
          message: ''
        });
        setSubmissionMessage('Application Submitted! We will be with you within 48 hours!');
        toast.success('Application Submitted! We will be with you within 48 hours!');
        navigate('/applynow');
      }
    } catch (error) {
      console.log(error);
    }
  };
    return (
      <div>

            <header className="header">
      <a href="/">
        <img className="logo" alt="TBS logo" src="tbs logo.png" />
      </a>

      <nav className="main-nav">
        <ul className="main-nav-list">
          <li><a className="main-nav-link" href="http://www.msn.com">Traffic Control</a></li>
          <li><a className="main-nav-link" href="#how">Clothing and Bags</a></li>
          <li>
            <a className="main-nav-link" href="#how">Banners and Posters</a>
          </li>
          <li><a className="main-nav-link" href="#how">Business Cards</a></li>
          <li><a className="main-nav-link" href="#how">Window Vinyls</a></li>
          <li><a className="main-nav-link" href="#meals">Items</a></li>
          <li>
            <a className="main-nav-link" href="#testimonials">Signs</a>
          </li>
          <li><a className="main-nav-link" href="#pricing">Pricing</a></li>
          <li>
            <a className="main-nav-link nav-cta" href="#cta">Get a free quote</a>
          </li>
        </ul>
      </nav>

      <button className="btn-mobile-nav">
        <ion-icon className="icon-mobile-nav" name="menu-outline"></ion-icon>
        <ion-icon className="icon-mobile-nav" name="close-outline"></ion-icon>
      </button>
    </header>

        <main>
        <div className="apply-container">
          <h1 className="apply-now">APPLY NOW</h1>
          <h2 className="descript">Discover a career with TBS, 
          a premier leader in traffic control solutions! 
          As a dynamic and rapidly growing company in the traffic management industry, 
          TBS takes pride in revolutionizing how we navigate and manage traffic flow. 
          Join our dedicated team and contribute to creating safer, 
          more efficient roadways.</h2>
        </div>
        <form className="apply-set -- box" id="applicationForm" onSubmit={handleSubmit}>
          <div className="container container--narrow page-section">

          <h1 className="job-app-box">Job Application Form</h1>
          <h2 className="job-fill">Please Fill Out the Form Below to Submit Your Job Application!</h2>


          <label className="name">Name: </label>
        <div className="first-input">

          <div className="first-name">
              <div className="name-input">
                <label className="first-label-name">First Name *</label>
                  <input name="first" type="text" className="first-name-input"text="first-name--input" placeholder="Enter First Name" 
                 value={data.first} onChange={(e) => formData({...data, first: e.target.value})} required />
              </div>
              </div>
            <div className="last-name">
              <div className="name-input"> 
            <label className="last-label-name">Last Name *</label>
              <input name="last" type="text"className="last-name-input"text="last-name--input" placeholder="Enter Last Name"
              value={data.last} onChange={(e) => formData({...data, last: e.target.value})}required />
              </div>
            </div>
            </div>
            <label className="emailphone-label">Email/Phone Number:</label>

            <div className="emailphone-input">

            <div className="email">
            <div className="name-input">
            <label className="email-name">Email *</label>
              <input name="email" type="text" className="email-box"text="email--input" placeholder="Enter Email"
              value={data.email} onChange={(e) => formData({...data, email: e.target.value})} required />
              </div> 
              </div>

            <div className="phone">
            <div className="name-input">
            <label className="phone">Phone Number *</label>
              <input name="phone" type="text" className="phone-box"text="phone--input" placeholder="Enter Phone Number"
              value={data.phone} onChange={(e) => formData({...data, phone: e.target.value})}required />
              </div>
              </div>  
              </div>  

            <label className="resume-label">Resume/Cover Letter:</label>
              <h1 className="resume-note">Note: You can only submit .doc, .pdf, .txt, and .pages files</h1>
              <div className="resume-input">
        <div className="resume">
          <div className="name-input">
            <label htmlFor="resume" className="resume-name">Resume *</label>
            <div className="file-input-container">
              <label className="file-label"></label>
              <FileInput
                type="file"
                id="resume"
                className="fileInput"
                accept=".pdf,.doc,.docx,.txt,.pages" 
                value={data.resume} onChange={(e) => formData({...data, resume: e.target.files[0]})}required
              />
            </div>
          </div>
        </div>
      </div>
      <div className="cover-letter">
        <div className="name-input">
          <label className="cover-name">Cover Letter</label>
          <div className="file-input-container">
            <label className="file-label"></label>
            <FileInput
              type="file"
              id="coverLetter"
              className="fileInput"
              accept=".pdf,.doc,.docx,.txt,.pages"
              value={data.cover} onChange={(e) => formData({...data, cover: e.target.files[0]})} />

          </div>
        </div>
      </div>
            <label className="message-label">Message: </label>
            <h1 className="message-note">Tell us why you want to work for TBS! </h1>

                <textarea className="message-text" name="message" type="text" placeholder="Enter Message"
                value={data.message} onChange={(e) => formData({...data, message: e.target.value})}required />
                  <button type="submit" className="btn btn--full submit-app">Submit Application</button>
          </div>
          {submissionErrorMessage && (
            <div className="submission-error-message">{submissionErrorMessage}</div>
          )}
          {submissionMessage && (
          <div className="submission-message">{submissionMessage}</div>
         )}
         </form>

         </main>
         <footer className="footer">
                  <div className="site-footer__inner container container--narrow">

                        <h1 className="tbs-copy">
                          <a href="index.html">&copy; 2024 Traffic & Barrier Solutions, LLC</a>
                        </h1>
                        <h1 className="footer-number">706-263-0175</h1>
                      </div>

            </footer>

        </div>
    )

    }
Enter fullscreen mode Exit fullscreen mode

The Apply Now page has a file imported that has the Resume and Cover Letter Choose File buttons like this(./components/FileInput.jsx):

// FileInput.js
import React, { useState } from 'react';
import '../css/FileInput.css';

const FileInput = () => {
  const [fileNames, setFileNames] = useState([]);

  const updateFileNames = (event) => {
    const files = event.target.files;
    const names = Array.from(files).map(file => file.name);
    setFileNames(names);
  };

  return (
    <div className="file-input-container">
      <label htmlFor="fileInput" className="file-label">
        {fileNames.length > 0 ? fileNames.join(', ') : 'Choose a file...'}
      </label>
      <input
        type="file"
        id="fileInput"
        className="fileInput"
        onChange={updateFileNames}
        multiple // Allow multiple file selection
      />
    </div>
  );
};

export default FileInput;
Enter fullscreen mode Exit fullscreen mode

Now onto the Server Side. The server side has the components, routes, and models folders. ../components/authControl.js:

const Application = require('../models/user');
const nodemailer = require('nodemailer')

const test = (req, res) => {
    res.json('Test is Working')
}

const submitApplication = async (req, res) => {
  try {
    const {
      first,
      last,
      email,
      phone,
      resume,
      cover,
      message
    } = req.body;


    let transporter = nodemailer.createTransport({
      host: 'smtp.gmail.com',
      port: 587,
      secure: false,
      auth: {
        user: 'tbsolutions9@gmail.com',
        pass: 'password!'
      }
    });
    // check for email

    const existingApp = await Application.findOne({ $or: [{ email }, { phone }] });
    if (existingApp) {
        return res.status(400).json({
          error: "Application has already been submitted with this email or phone number. If you recently worked for TBS, please call 706-263-0175. If you're new and have submitted before, please wait until we review your application."
        });
      }


      const newApp = await Application.create({ first, last, email, phone, resume, cover, message });

      await transporter.sendMail({
        from: 'tbsolutions9@gmail.com',
        to: email,
        subject: 'Application Submitted!',
        text: 'Thank you for submitting your application to TBS. We will be with you within 48 hours.'
      })

      return res.json(newApp);
    } catch (error) {
      console.log(error);
      return res.status(500).json({ error: 'Internal Server Error' });
    }
  };


    // Create a new application entry


    // Save the application to the database

module.exports = {
    test,
    submitApplication
}
Enter fullscreen mode Exit fullscreen mode

./routes/autoRoute.js:

const express = require('express');
const router = express.Router();
const cors = require('cors')
const { test, submitApplication } = require('../controllers/authControl');


// Route for submitting an application


router.use(
    cors({
        credentials: true,
        origin: 'http://localhost:5173'
    })
)


router.get('/', test)

router.post('/applynow', submitApplication);


module.exports = router;
Enter fullscreen mode Exit fullscreen mode

server/index.js:

const express = require('express');
const dotenv = require('dotenv').config();
const {mongoose} = require('mongoose');
const app = express();

//Import removeDuplicates function
const removeDuplicates = require('./controllers/duplicateHandler');

// Database connection
mongoose.connect(process.env.MONGO_URL)
  .then(() => {
    console.log('Database Connected');
    // Call removeDuplicates function after database connection
    removeDuplicates();
  })
  .catch((err) => console.log('Database Not Connected', err));


// Middleware
app.use(express.json());

// Routes
app.use('/', require('./routes/autoRoute'));

const port = 8000;
app.listen(port, () => console.log(`Server is running on port ${port}`));
Enter fullscreen mode Exit fullscreen mode

../components/duplicateHandler.js:

const Application = require('../models/user');

async function removeDuplicates() {
    try {
        await Application.deleteMany({resume: ""});
      // Step 1: Identify duplicate documents
      const duplicateDocs = await Application.aggregate([
        {
          $group: {
            _id: {
              key: {
                first: "$first",
                last: "$last",
                email: "$email",
                phone: "$phone",
                resume: "$resume",
                cover: "$cover",
                message: "$message"
              }
            },
            count: { $sum: 1 },
            ids: { $push: "$_id" }
          }
        },
        {
          $match: {
            count: { $gt: 1 } // Filter groups with more than one document
          }
        }
      ]);

      // Step 2 & 3: Determine which duplicates to keep and remove the rest
      for (const doc of duplicateDocs) {
        // Keep the first document and remove the rest
        const [toKeep, ...toRemove] = doc.ids;
        await Application.deleteMany({ _id: { $in: toRemove } });
      }

      console.log("Duplicates removed successfully.");
    } catch (error) {
      console.error('Error while removing duplicates:', error);
    }
  }

module.exports = removeDuplicates;
Enter fullscreen mode Exit fullscreen mode

./models/user.js:

const mongoose = require('mongoose');
const {Schema} = mongoose

const applicationSchema = new Schema({
  first: { 
    type: String, 
    unique: true 
},
  last: { 
    type: String, 
    unique: true  
},
  email: { 
    type: String, 
    unique: true 
},
  phone: { 
    type: String, 
    unique: true 
},
  resume: { 
    type: String, 
    unique: true 
},
  cover: { 
    type: String 
},
  message: { 
    type: String, 
    unique: true 
}
});

const Application = mongoose.model('Application', applicationSchema);

module.exports = Application;
Enter fullscreen mode Exit fullscreen mode

I am trying to make the apply now page to where if it detects duplicate emails or phone numbers that the server end will send a message to the user that the email or phone number has already been submitted and is saved in the MongoDB database. However, I also need the apply now page made to when someone fills out their First Name, Last Name, Email, Phone Number, Resume Choose File, Cover Letter Choose File, and a brief Message in HTML. The form has to have everything except the cover letter required. And I need to know how to add email address(specifically gmail addresses) for the server side to send when the user on the client side fills out the form and then it can display a success message sent and then we get emails back from the user. If I need to change anything from my lines of code or add anymore .js files in my server folder for the backend to send emails? If I need to add anymore in the folders or any questions, please let me know! Note: the password in the authControl.js code is named

password

because I don't want to show my password to my gmail. That is correct.
I have all my code on my Apply Now page on my GitHub repository: https://github.com/trafficBSolutions/TBS-Website

Top comments (0)