MULTI-STEP JOB APPLICATION FORM


npm create vue@latest p4
Select:
Pinia: Yes



npm install
npm install axios express mongoose cors dotenv
npm install -D nodemon



mkdir backend
cd backend
npm init -y
npm install express mongoose cors dotenv
npm install -D nodemon



backend/package.json — Replace the scripts section:
"scripts": {
  "start": "node server.js",
  "dev": "nodemon server.js"
}



backend/.env
MONGO_URI=mongodb+srv://username:password@cluster.mongodb.net/jobs



backend/server.js
const express = require('express')
const mongoose = require('mongoose')
const cors = require('cors')
require('dotenv').config()

const app = express()
app.use(cors())
app.use(express.json())

mongoose.connect(process.env.MONGO_URI)

const Application = mongoose.model('Application', new mongoose.Schema({
  personal: Object,
  education: Object,
  experience: Array
}))

app.post('/api/applications', async (req, res) => {
  try {
    const data = await Application.create(req.body)
    res.json(data)
  } catch (err) {
    res.status(500).json({ error: err.message })
  }
})

app.get('/api/applications', async (req, res) => {
  res.json(await Application.find())
})

app.listen(5000, () => console.log('Server running on port 5000'))






src/stores/applicationStore.js
import { defineStore } from 'pinia'
import axios from 'axios'

const api = 'http://localhost:5000/api/applications'

export const useApplicationStore = defineStore('applications', {
  state: () => ({ applications: [] }),
  actions: {
    async submit(data) {
      await axios.post(api, data)
      await this.fetch()
    },
    async fetch() {
      const res = await axios.get(api)
      this.applications = res.data
    }
  }
})




src/App.vue
<script setup>
import { ref, computed } from 'vue'
import { useApplicationStore } from './stores/applicationStore'

const store = useApplicationStore()
const step = ref(1)
const personal = ref({ name: '', email: '', phone: '', dob: '' })
const education = ref({ degree: '', institution: '', year: '' })
const experience = ref([])
const message = ref('')

const valid = computed(() => {
  const p = personal.value
  const e = education.value
  const year = Number(e.year)
  const currentYear = new Date().getFullYear()

  if (step.value === 1) {
    const dob = new Date(p.dob)
    const age = new Date().getFullYear() - dob.getFullYear() -
      (new Date() < new Date(new Date().getFullYear(),
      dob.getMonth(), dob.getDate()) ? 1 : 0)

    return p.name.trim() &&
      /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(p.email) &&
      /^\d{10}$/.test(p.phone) &&
      p.dob && !isNaN(dob) && dob < new Date() && age >= 18
  }

  if (step.value === 2)
    return e.degree.trim() && e.institution.trim() &&
      Number.isInteger(year) && year >= 1900 && year < currentYear

  if (step.value === 3)
    return experience.value.every(j =>
      j.company.trim() && j.role.trim() &&
      (!j.start || !j.end || j.end > j.start)
    )

  return true
})

function addJob() {
  experience.value.push({ company: '', role: '', start: '', end: '' })
}

async function submit() {
  try {
    await store.submit({
      personal: personal.value,
      education: education.value,
      experience: experience.value
    })
    message.value = 'Application submitted successfully!'
  } catch {
    message.value = 'Submission failed!'
  }
}
</script>

<template>
  <main class="container">
    <h1>Job Application</h1>
    <p class="progress">Step {{ step }} of 4</p>
    <div class="bar"><div :style="{width: step * 25 + '%'}"></div></div>

    <section v-if="step === 1">
      <h2>Personal Details</h2>
      <input v-model="personal.name" placeholder="Full Name">
      <input v-model="personal.email" placeholder="Email">
      <input v-model="personal.phone" placeholder="10-digit Phone">
      <label>Date of Birth</label>
      <input v-model="personal.dob" type="date">
    </section>

    <section v-if="step === 2">
      <h2>Education Details</h2>
      <input v-model="education.degree" placeholder="Degree">
      <input v-model="education.institution" placeholder="Institution">
      <input v-model="education.year" type="number" placeholder="Passing Year">
    </section>

    <section v-if="step === 3">
      <h2>Work Experience</h2>
      <div v-for="(job, i) in experience" :key="i" class="job">
        <input v-model="job.company" placeholder="Company">
        <input v-model="job.role" placeholder="Role">
        <label>Start Date</label>
        <input v-model="job.start" type="date">
        <label>End Date</label>
        <input v-model="job.end" type="date">
        <button class="remove" @click="experience.splice(i, 1)">Remove</button>
      </div>
      <button @click="addJob">Add Work Experience</button>
      <p>Work experience is optional.</p>
    </section>

    <section v-if="step === 4">
      <h2>Review Application</h2>
      <h3>Personal Details</h3>
      <p v-for="(v, k) in personal" :key="k">{{ k }}: {{ v }}</p>
      <h3>Education</h3>
      <p v-for="(v, k) in education" :key="k">{{ k }}: {{ v }}</p>
      <h3>Work Experience</h3>
      <div v-for="(job, i) in experience" :key="i">
        <p>{{ job.company }} - {{ job.role }} ({{ job.start }} to {{ job.end }})</p>
      </div>
      <p v-if="!experience.length">No work experience</p>
      <button @click="submit">Submit Application</button>
    </section>
    <div class="buttons">
      <button v-if="step > 1" class="back" @click="step--">Back</button>
      <button v-if="step < 4" :disabled="!valid" @click="step++">Next</button>
    </div>
    <p class="message">{{ message }}</p>
  </main>
</template>



src/main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import './style.css'
createApp(App).use(createPinia()).mount('#app')



src/style.css
* { box-sizing: border-box; }
body {
  margin: 0;
  font-family: Arial, sans-serif;
  background: #eef2ff;
  color: #1e293b;
}
.container {
  max-width: 600px;
  margin: 35px auto;
  padding: 30px;
  background: white;
  border-radius: 14px;
  box-shadow: 0 5px 25px #0002;
}

h1 { color: #4f46e5; text-align: center; }
h2 { color: #334155; }

input {
  width: 100%;
  padding: 12px;
  margin: 9px 0;
  border: 1px solid #cbd5e1;
  border-radius: 7px;
}

label { display: block; font-size: 13px; }

button {
  padding: 11px 20px;
  margin: 8px 5px;
  border: 0;
  border-radius: 7px;
  background: #4f46e5;
  color: white;
  cursor: pointer;
}

button:disabled { background: #94a3b8; cursor: not-allowed; }
.back, .remove { background: #64748b; }

.job {
  padding: 15px;
  margin: 12px 0;
  background: #f1f5f9;
  border-radius: 8px;
}

.progress { text-align: center; color: #64748b; }

.bar {
  height: 7px;
  background: #e2e8f0;
  border-radius: 10px;
}

.bar div {
  height: 100%;
  background: #4f46e5;
  border-radius: 10px;
}

.buttons { display: flex; justify-content: space-between; }
.message { color: #059669; text-align: center; }





Terminal 1 – Backend
cd p4/backend
npm run dev

Terminal 2 – Frontend
cd p4
npm run dev

