MULTI-STEP JOB APPLICATION FORM - COMPLETE CORRECTED CODE
========================================================================

This is a consolidated Notepad-style copy of the complete project.
Create each file at the path shown in its section heading.
The code uses Vue 3, Pinia, Vite, Express and MongoDB.


========================================================================
PROJECT STRUCTURE
========================================================================

Job-Application-Project/
|
|-- package.json
|-- index.html
|-- vite.config.js
|
|-- src/
|   |-- main.js
|   |-- App.vue
|   |-- style.css
|   |-- stores/
|   |   |-- jobapplication.js
|   |-- components/
|       |-- JobApplicationForm.vue
|
|-- backend/
    |-- server.cjs
    |-- .env

IMPORTANT:
Use the exact file names and folders shown above.
The Pinia store file is named jobapplication.js, and the component imports
that exact file. The Vue component is named JobApplicationForm.vue.


========================================================================
SETUP INSTRUCTIONS
========================================================================

1. Open the Job-Application-Project folder in VS Code.
2. Open the VS Code terminal in the project root folder.
3. Run:
   npm install

4. Inside the backend folder, create a file named .env.
   You can copy backend/.env.example and rename the copy to .env.

5. For local MongoDB, put this in backend/.env:
   MONGO_URI=mongodb://127.0.0.1:27017/jobApplications
   PORT=5000

   OR, for MongoDB Atlas, use your Atlas connection string:
   MONGO_URI=mongodb+srv://USERNAME:PASSWORD@YOUR_CLUSTER.mongodb.net/jobApplications
   PORT=5000

   Replace USERNAME, PASSWORD and YOUR_CLUSTER with your own Atlas details.
   Do not add mongodb: before mongodb+srv://.
   Do not share your database password or upload .env to GitHub.

6. Make sure MongoDB is running (local MongoDB) or your Atlas cluster is
   available and your IP address is allowed in Atlas Network Access.

7. From the project root, run:
   npm run dev

8. Open the frontend:
   http://localhost:5173

9. Backend test:
   http://localhost:5000

10. To view saved applications:
    http://localhost:5000/api/applications

The frontend and backend run together with npm run dev.


========================================================================
FILE: package.json
========================================================================

{
  "name": "job-application-project",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "concurrently -k \"vite\" \"npm:server\"",
    "server": "node backend/server.cjs",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "cors": "^2.8.5",
    "dotenv": "^16.4.5",
    "express": "^4.21.2",
    "mongoose": "^8.9.5",
    "pinia": "^2.3.0",
    "vue": "^3.5.13"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^5.2.1",
    "concurrently": "^9.1.2",
    "vite": "^6.0.7"
  }
}

========================================================================
FILE: index.html
========================================================================

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Job Application Portal</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>

========================================================================
FILE: vite.config.js
========================================================================

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  server: {
    port: 5173
  }
})

========================================================================
FILE: src/main.js
========================================================================

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import './style.css'

const app = createApp(App)
app.use(createPinia())
app.mount('#app')

========================================================================
FILE: src/App.vue
========================================================================

<script setup>
import JobApplicationForm from './components/JobApplicationForm.vue'
</script>

<template>
  <main>
    <JobApplicationForm />
  </main>
</template>

========================================================================
FILE: src/stores/jobapplication.js
========================================================================

import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

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

export const useApplicationStore = defineStore('application', () => {
  const personalDetails = ref({
    name: '',
    email: '',
    phone: '',
    dateOfBirth: ''
  })

  const education = ref({
    degree: '',
    institution: '',
    yearOfPassing: ''
  })

  const workExperience = ref([])
  const currentStep = ref(1)
  const totalSteps = 4

  const isPersonalValid = computed(() => {
    const p = personalDetails.value
    if (!p.name.trim()) return false
    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(p.email)) return false
    if (!/^\d{10}$/.test(p.phone)) return false
    if (!p.dateOfBirth) return false

    const dob = new Date(`${p.dateOfBirth}T00:00:00`)
    const today = new Date()
    if (Number.isNaN(dob.getTime()) || dob > today) return false

    let age = today.getFullYear() - dob.getFullYear()
    const monthDiff = today.getMonth() - dob.getMonth()
    if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < dob.getDate())) age--
    return age >= 18
  })

  const isEducationValid = computed(() => {
    const e = education.value
    const yearText = String(e.yearOfPassing)
    const year = Number(yearText)
    return Boolean(e.degree.trim() && e.institution.trim()) &&
      /^\d{4}$/.test(yearText) &&
      year >= 1801 &&
      year <= new Date().getFullYear()
  })

  const isWorkValid = computed(() => workExperience.value.every(exp => {
    if (!exp.company.trim() || !exp.role.trim()) return false
    if (exp.startDate && exp.endDate) {
      return new Date(`${exp.endDate}T00:00:00`) > new Date(`${exp.startDate}T00:00:00`)
    }
    return true
  }))

  const isStepValid = computed(() => {
    if (currentStep.value === 1) return isPersonalValid.value
    if (currentStep.value === 2) return isEducationValid.value
    if (currentStep.value === 3) return isWorkValid.value
    return true
  })

  function nextStep() {
    if (isStepValid.value && currentStep.value < totalSteps) currentStep.value++
  }

  function previousStep() {
    if (currentStep.value > 1) currentStep.value--
  }

  function addWorkExperience() {
    workExperience.value.push({
      company: '',
      role: '',
      startDate: '',
      endDate: ''
    })
  }

  function removeWorkExperience(index) {
    workExperience.value.splice(index, 1)
  }

  function resetForm() {
    personalDetails.value = { name: '', email: '', phone: '', dateOfBirth: '' }
    education.value = { degree: '', institution: '', yearOfPassing: '' }
    workExperience.value = []
    currentStep.value = 1
  }

  async function submitApplication() {
    const payload = {
      personalDetails: { ...personalDetails.value },
      education: { ...education.value, yearOfPassing: Number(education.value.yearOfPassing) },
      workExperience: workExperience.value.map(exp => ({ ...exp }))
    }

    const response = await fetch(API_URL, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload)
    })

    const result = await response.json()
    if (!response.ok) throw new Error(result.error || 'Application submission failed')
    return result
  }

  return {
    personalDetails, education, workExperience, currentStep, totalSteps,
    isPersonalValid, isEducationValid, isWorkValid, isStepValid,
    nextStep, previousStep, addWorkExperience, removeWorkExperience,
    resetForm, submitApplication
  }
})

========================================================================
FILE: src/components/JobApplicationForm.vue
========================================================================

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

const store = useApplicationStore()
const submitting = ref(false)
const success = ref(false)
const error = ref('')
const currentYear = new Date().getFullYear()

const progress = computed(() => (store.currentStep / store.totalSteps) * 100)

function formatDate(date) {
  if (!date) return 'Not specified'
  return new Date(`${date}T00:00:00`).toLocaleDateString()
}

async function submit() {
  submitting.value = true
  error.value = ''
  try {
    await store.submitApplication()
    success.value = true
  } catch (err) {
    error.value = err.message || 'Unable to submit. Check the backend and MongoDB.'
  } finally {
    submitting.value = false
  }
}

function newApplication() {
  store.resetForm()
  success.value = false
  error.value = ''
}
</script>

<template>
  <div class="form-container">
    <h1>Job Application Form</h1>

    <div v-if="success" class="success">
      <h2>Application submitted successfully!</h2>
      <p>Your application has been saved.</p>
      <button @click="newApplication">Start New Application</button>
    </div>

    <template v-else>
      <div class="step-row">
        <strong>Step {{ store.currentStep }} of {{ store.totalSteps }}</strong>
        <span>{{ Math.round(progress) }}% complete</span>
      </div>
      <div class="progress">
        <div class="progress-fill" :style="{ width: progress + '%' }"></div>
      </div>

      <section v-if="store.currentStep === 1">
        <h2>1. Personal Details</h2>

        <label for="name">Full Name *</label>
        <input id="name" v-model="store.personalDetails.name" placeholder="Enter your full name">

        <label for="email">Email *</label>
        <input id="email" v-model="store.personalDetails.email" type="email" placeholder="name@example.com">

        <label for="phone">Phone *</label>
        <input id="phone" v-model="store.personalDetails.phone" type="tel" maxlength="10" placeholder="10-digit phone number">

        <label for="dob">Date of Birth *</label>
        <input id="dob" v-model="store.personalDetails.dateOfBirth" type="date" :max="new Date().toISOString().slice(0, 10)">

        <p v-if="!store.isPersonalValid" class="hint">Enter a name, valid email, 10-digit phone number and date of birth. Applicant must be at least 18.</p>
      </section>

      <section v-if="store.currentStep === 2">
        <h2>2. Education</h2>

        <label for="degree">Degree *</label>
        <input id="degree" v-model="store.education.degree" placeholder="e.g. B.Sc. Computer Science">

        <label for="institution">Institution *</label>
        <input id="institution" v-model="store.education.institution" placeholder="Enter institution name">

        <label for="year">Year of Passing *</label>
        <input id="year" v-model="store.education.yearOfPassing" type="number" min="1801" :max="currentYear" placeholder="e.g. 2025">

        <p v-if="!store.isEducationValid" class="hint">Enter degree, institution and a four-digit past year (1801–{{ currentYear }}).</p>
      </section>

      <section v-if="store.currentStep === 3">
        <h2>3. Work Experience</h2>
        <p class="hint">Optional. You can continue without adding any experience.</p>

        <div v-for="(exp, index) in store.workExperience" :key="index" class="experience">
          <div class="experience-heading">
            <h3>Experience {{ index + 1 }}</h3>
            <button type="button" class="remove" @click="store.removeWorkExperience(index)">Remove</button>
          </div>

          <label :for="'company-' + index">Company *</label>
          <input :id="'company-' + index" v-model="exp.company" placeholder="Company name">

          <label :for="'role-' + index">Role *</label>
          <input :id="'role-' + index" v-model="exp.role" placeholder="Job title or role">

          <label :for="'start-' + index">Start Date</label>
          <input :id="'start-' + index" v-model="exp.startDate" type="date">

          <label :for="'end-' + index">End Date</label>
          <input :id="'end-' + index" v-model="exp.endDate" type="date">

          <p v-if="exp.startDate && exp.endDate && exp.endDate <= exp.startDate" class="error">End date must be after start date.</p>
          <p v-if="(!exp.company.trim() || !exp.role.trim())" class="hint">Company and role are required for each entry.</p>
        </div>

        <button type="button" class="secondary" @click="store.addWorkExperience">+ Add Work Experience</button>
      </section>

      <section v-if="store.currentStep === 4">
        <h2>4. Review & Submit</h2>

        <div class="review-section">
          <h3>Personal Details</h3>
          <p><strong>Name:</strong> {{ store.personalDetails.name }}</p>
          <p><strong>Email:</strong> {{ store.personalDetails.email }}</p>
          <p><strong>Phone:</strong> {{ store.personalDetails.phone }}</p>
          <p><strong>Date of Birth:</strong> {{ formatDate(store.personalDetails.dateOfBirth) }}</p>
        </div>

        <div class="review-section">
          <h3>Education</h3>
          <p><strong>Degree:</strong> {{ store.education.degree }}</p>
          <p><strong>Institution:</strong> {{ store.education.institution }}</p>
          <p><strong>Year of Passing:</strong> {{ store.education.yearOfPassing }}</p>
        </div>

        <div class="review-section">
          <h3>Work Experience</h3>
          <p v-if="store.workExperience.length === 0">No work experience provided.</p>
          <div v-for="(exp, index) in store.workExperience" :key="index" class="review-entry">
            <strong>Experience {{ index + 1 }}</strong>
            <p><strong>Company:</strong> {{ exp.company }}</p>
            <p><strong>Role:</strong> {{ exp.role }}</p>
            <p><strong>Period:</strong> {{ formatDate(exp.startDate) }} – {{ formatDate(exp.endDate) }}</p>
          </div>
        </div>

        <button @click="submit" :disabled="submitting">
          {{ submitting ? 'Submitting...' : 'Submit Application' }}
        </button>
        <p v-if="error" class="error">{{ error }}</p>
      </section>

      <div class="navigation">
        <button v-if="store.currentStep > 1" class="secondary" @click="store.previousStep">Back</button>
        <button v-if="store.currentStep < 4" @click="store.nextStep" :disabled="!store.isStepValid">Next</button>
      </div>
    </template>
  </div>
</template>

========================================================================
FILE: src/style.css
========================================================================

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  padding: 24px 14px;
  background: #f2f2f2;
  color: #333;
  font-family: Arial, sans-serif;
}

.form-container {
  max-width: 560px;
  margin: 20px auto;
  padding: 24px;
  background: #fff;
  border: 1px solid #ddd;
  border-radius: 8px;
}

h1 {
  margin-top: 0;
  text-align: center;
}

h2 {
  margin-top: 22px;
  color: #333;
}

label {
  display: block;
  margin-top: 14px;
  font-weight: 600;
}

input {
  display: block;
  width: 100%;
  margin-top: 6px;
  padding: 10px;
  border: 1px solid #ccc;
  border-radius: 5px;
  font-size: 14px;
}

input:focus {
  outline: 2px solid #bfdbfe;
  border-color: #2196f3;
}

button {
  margin-top: 16px;
  padding: 10px 15px;
  border: 0;
  border-radius: 5px;
  background: #2196f3;
  color: #fff;
  cursor: pointer;
}

button:disabled {
  background: #aaa;
  cursor: not-allowed;
}

.secondary {
  background: #666;
}

.remove {
  margin-top: 0;
  background: #d93025;
}

.step-row,
.experience-heading,
.navigation {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.step-row {
  margin-top: 22px;
  font-size: 14px;
}

.progress {
  height: 8px;
  margin: 10px 0 22px;
  overflow: hidden;
  border-radius: 5px;
  background: #ddd;
}

.progress-fill {
  height: 100%;
  background: #2196f3;
  transition: width 0.2s;
}

.experience,
.review-section {
  margin: 16px 0;
  padding: 14px;
  border: 1px solid #ddd;
  border-radius: 5px;
}

.experience h3,
.review-section h3 {
  margin: 0 0 10px;
}

.review-section p,
.review-entry p {
  margin: 7px 0;
  overflow-wrap: anywhere;
}

.review-entry {
  padding: 10px 0;
  border-top: 1px solid #eee;
}

.hint {
  color: #777;
  font-size: 13px;
  line-height: 1.4;
}

.error {
  color: #c62828;
  font-size: 13px;
}

.success {
  padding: 20px 0;
  color: #237443;
  text-align: center;
}

.navigation {
  justify-content: space-between;
  margin-top: 20px;
}

@media (max-width: 480px) {
  .form-container {
    padding: 17px;
  }
}

========================================================================
FILE: backend/server.cjs
========================================================================

const express = require('express')
const mongoose = require('mongoose')
const cors = require('cors')
require('dotenv').config({ path: 'backend/.env' })

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

const applicationSchema = new mongoose.Schema({
  personalDetails: {
    name: { type: String, required: true, trim: true },
    email: { type: String, required: true, trim: true },
    phone: { type: String, required: true },
    dateOfBirth: { type: String, required: true }
  },
  education: {
    degree: { type: String, required: true, trim: true },
    institution: { type: String, required: true, trim: true },
    yearOfPassing: { type: Number, required: true }
  },
  workExperience: [{
    company: { type: String, required: true, trim: true },
    role: { type: String, required: true, trim: true },
    startDate: { type: String, default: '' },
    endDate: { type: String, default: '' }
  }],
  submittedAt: { type: Date, default: Date.now }
})

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

app.get('/', (req, res) => {
  res.send('Job Application API is running')
})

app.post('/api/applications', async (req, res) => {
  try {
    const application = new Application(req.body)
    await application.save()
    res.status(201).json(application)
  } catch (error) {
    res.status(400).json({ error: error.message })
  }
})

app.get('/api/applications', async (req, res) => {
  try {
    const applications = await Application.find().sort({ submittedAt: -1 })
    res.json(applications)
  } catch (error) {
    res.status(500).json({ error: error.message })
  }
})

const PORT = process.env.PORT || 5000
const MONGO_URI = process.env.MONGO_URI

if (!MONGO_URI) {
  console.error('MONGO_URI is missing. Add it to backend/.env')
  process.exit(1)
}

mongoose.connect(MONGO_URI)
  .then(() => {
    console.log('MongoDB connected')
    app.listen(PORT, () => console.log(`Backend running at http://localhost:${PORT}`))
  })
  .catch(error => {
    console.error('MongoDB connection failed:', error.message)
    process.exit(1)
  })

========================================================================
FILE: backend/.env.example
========================================================================

# For local MongoDB:
MONGO_URI=mongodb://127.0.0.1:27017/jobApplications

# For MongoDB Atlas, replace the line above with your Atlas connection string.

========================================================================
END OF COMPLETE CODE
========================================================================
