Vue 3 single-page application for the HireFlow recruitment platform. Supports two user roles β Employer and Candidate β each with a dedicated dashboard and feature set.
- Tech Stack
- Prerequisites
- Getting Started
- Environment Variables
- Project Structure
- Routes & Pages
- State Management
- API Integration
- Authentication Flow
- Component Library
- Design System
- Testing
- Docker
| Category | Library / Tool | Version |
|---|---|---|
| Framework | Vue 3 (Composition API) | 3.x |
| Build Tool | Vite | 7.3.1 |
| Language | TypeScript | ~5.9.3 |
| Routing | Vue Router | 5.0.3 |
| State Management | Pinia | 3.0.4 |
| Styling | Tailwind CSS | 4.2.1 |
| UI Components | Reka UI (headless) | 2.9.2 |
| Icons | Lucide Vue Next | 0.577.0 |
| HTTP Client | Axios | 1.13.6 |
| Form Validation | Vee-Validate + Zod | 4.15.1 / 3.25.76 |
| Tables | TanStack Vue Table | 8.21.3 |
| Toast Notifications | Vue Sonner | 2.0.9 |
| Date Picker | Vue DatePicker | 12.1.0 |
| Composition Utilities | VueUse | 14.2.1 |
| Testing | Vitest + Vue Test Utils | 4.1.0 / 2.4.6 |
| Formatting | Prettier | 3.8.1 |
- Node.js 18+
- npm 9+
- All backend microservices running (see Backend README)
# Navigate to the frontend directory
cd frotend
# Install dependencies
npm install
# Start the development server (http://localhost:5173)
npm run dev| Command | Description |
|---|---|
npm run dev |
Start development server with hot reload |
npm run build |
Type-check then bundle for production (dist/) |
npm run preview |
Serve the production build locally |
npm run type-check |
Run vue-tsc type validation |
npm run test:unit |
Run unit tests with Vitest |
npm run test:cov |
Run tests with v8 coverage report |
npm run format |
Format src/ with Prettier |
Create a .env file in the frotend/ directory. An .env.example file is provided as a reference.
VITE_AUTH_API_URL=http://localhost:8081
VITE_JOB_API_URL=http://localhost:3090
VITE_INTERVIEW_API_URL=http://localhost:3000
VITE_COMPANY_API_URL=http://localhost:8080/api
VITE_CV_API_URL=http://localhost:8085All variables are prefixed with VITE_ and accessed in code via import.meta.env.VITE_*.
For production, duplicate to .env.production and replace localhost URLs with your deployed service URLs.
frotend/
βββ public/ # Static assets served as-is
βββ src/
β βββ main.ts # App entry point β mounts Vue, registers Pinia & Router
β βββ App.vue # Root component β hosts <RouterView> and <Toaster>
β βββ constants.ts # Shared enums (UserRole, HiringStage, etc.) and route names
β β
β βββ router/
β β βββ index.ts # Route definitions and navigation guards
β β
β βββ stores/ # Pinia stores (one per domain)
β β βββ auth.ts # Session, tokens, user identity
β β βββ jobs.ts # Job listings (employer & public)
β β βββ interview.ts # Interview scheduling
β β βββ pipeline.ts # Hiring pipeline progression
β β βββ company.ts # Company profiles & analytics
β β βββ cv.ts # Candidate CV & applications
β β
β βββ services/ # Axios API clients (one per microservice)
β β βββ api.ts # Auth service client + shared request interceptor
β β βββ jobApi.ts # Job Listing Service client
β β βββ interviewApi.ts # Interview Service client
β β βββ pipelineApi.ts # Hiring Pipeline endpoints (Interview Service)
β β βββ companyApi.ts # Profile Service client
β β βββ cvApi.ts # CV Service client
β β
β βββ views/ # Page-level components (matched to routes)
β β βββ HomeView.vue
β β βββ auth/
β β β βββ LoginView.vue
β β β βββ RegisterView.vue
β β βββ employer/
β β β βββ EmployerDashboardView.vue
β β β βββ EmployerJobsView.vue
β β β βββ EmployerJobApplicationsView.vue
β β β βββ PipelinesView.vue
β β β βββ PipelineDetailView.vue
β β β βββ InterviewsView.vue
β β β βββ CompanyProfileView.vue
β β βββ candidate/
β β βββ CandidateDashboardView.vue
β β βββ CandidateJobBoardView.vue
β β βββ CandidateInterviewsView.vue
β β βββ CandidatePipelineView.vue
β β βββ CandidateCvProfileView.vue
β β βββ ExploreCompaniesView.vue
β β βββ CompanyDetailsView.vue
β β
β βββ components/
β β βββ ui/ # Headless, reusable design-system components
β β βββ layout/
β β β βββ AppShell.vue # Authenticated layout (sidebar + header + slot)
β β β βββ AppSidebar.vue # Role-aware sidebar navigation
β β βββ jobs/
β β β βββ JobForm.vue # Create / edit job form
β β βββ interview/
β β β βββ InterviewTable.vue # Interview list table
β β βββ pipeline/
β β β βββ HiringStageProgress.vue # Visual stage progress bar
β β β βββ PipelineRow.vue # Single pipeline row
β β βββ icons/ # Custom SVG icon components
β β βββ ModeToggle.vue # Dark / light theme toggle
β β
β βββ types/ # TypeScript interfaces
β β βββ interview.ts # Interview, HiringPipeline, HiringStage types
β β βββ cv.ts # CandidateProfile, WorkExperience, Education types
β β
β βββ lib/
β β βββ utils.ts # cn() β Tailwind class merging (clsx + tailwind-merge)
β β
β βββ assets/
β βββ main.css # Global CSS β design tokens, dark mode, typography
β βββ base.css # CSS reset and base styles
β
βββ .env # Development environment variables
βββ .env.example # Reference template
βββ .env.production # Production environment variables
βββ vite.config.ts # Vite configuration
βββ vitest.config.ts # Vitest test configuration
βββ tsconfig.json # TypeScript configuration
βββ package.json
βββ Dockerfile # Container build
βββ nginx.conf # Nginx config for serving SPA in production
βββ index.html # HTML entry point
Route guards are applied globally. Routes with meta.requiresAuth redirect unauthenticated users to /login. Routes with meta.guest redirect authenticated users to their role dashboard. Role-specific routes check meta.role and redirect on mismatch.
| Path | Component | Description |
|---|---|---|
/login |
LoginView |
Email + password login |
/register |
RegisterView |
Account creation with role selection (Employer / Candidate) |
| Path | Component | Description |
|---|---|---|
/employer/dashboard |
EmployerDashboardView |
Stats overview, quick actions, recent activity |
/employer/jobs |
EmployerJobsView |
Create, edit, and close job postings |
/employer/jobs/:jobId/applications |
EmployerJobApplicationsView |
Review applications for a specific job |
/employer/pipelines |
PipelinesView |
All active hiring pipelines |
/employer/pipelines/:id |
PipelineDetailView |
Manage a single pipeline, advance candidates through stages |
/employer/interviews |
InterviewsView |
Schedule and manage interviews |
/employer/company |
CompanyProfileView |
Company info, logo upload, analytics |
| Path | Component | Description |
|---|---|---|
/candidate/dashboard |
CandidateDashboardView |
Application status summary, upcoming interviews |
/candidate/jobs |
CandidateJobBoardView |
Browse and search open jobs, submit applications |
/candidate/interviews |
CandidateInterviewsView |
View scheduled interviews, accept or decline |
/candidate/pipeline |
CandidatePipelineView |
Track application progress across all applied jobs |
/candidate/cv-profile |
CandidateCvProfileView |
Manage CV: bio, skills, work history, education, resume upload |
/candidate/companies |
ExploreCompaniesView |
Browse and follow companies |
/candidate/companies/:id |
CompanyDetailsView |
Company detail page with open positions |
Pinia stores follow the Composition API defineStore() pattern. All stores are in src/stores/.
Manages the active session. State is persisted to localStorage.
| State | Type | Description |
|---|---|---|
token |
string | null |
JWT access token |
refreshToken |
string | null |
JWT refresh token |
userId |
string | null |
Authenticated user ID |
name |
string | null |
User's display name |
role |
UserRole | null |
EMPLOYER or CANDIDATE |
| Getter | Description |
|---|---|
isAuthenticated |
true if token is present |
isEmployer |
true if role is EMPLOYER |
isCandidate |
true if role is CANDIDATE |
| Action | Description |
|---|---|
login(email, password) |
Authenticates and populates store |
register(payload) |
Registers and auto-logs-in |
logout() |
Calls logout API, clears all state |
| State | Description |
|---|---|
jobs |
All open jobs (public browse) |
myJobs |
Jobs posted by the current employer |
currentJob |
Single job detail |
loading / error |
Request status |
Actions: fetchMyJobs, createJob, updateJob, closeJob, fetchOpenJobs, searchJobs, fetchJobById
Actions: fetchForEmployer, fetchForCandidate, schedule, update, cancel, accept, decline
Actions: fetchForEmployer, fetchForCandidate, advanceStage
Actions: fetchAllCompanies, fetchCompanyById, search, fetchAnalytics, follow, unfollow, fetchByEmployee, createCompany, updateCompany, deleteCompany
Actions: fetchMyProfile, upsertProfile, uploadResume
Includes profile normalization to ensure consistent data shape before rendering.
Each backend microservice has a dedicated Axios client in src/services/. Every instance attaches the JWT token automatically via a request interceptor.
// Applied to all Axios instances
config.headers.Authorization = `Bearer ${localStorage.getItem('token')}`| File | Env Var | Target Service |
|---|---|---|
api.ts |
VITE_AUTH_API_URL |
Auth Service :8081 |
jobApi.ts |
VITE_JOB_API_URL |
Job Listing Service :3090 |
interviewApi.ts |
VITE_INTERVIEW_API_URL |
Interview Service :3000 |
pipelineApi.ts |
VITE_INTERVIEW_API_URL |
Interview Service :3000 (pipelines) |
companyApi.ts |
VITE_COMPANY_API_URL |
Profile Service :8080/api |
cvApi.ts |
VITE_CV_API_URL |
CV Service :8085 |
File uploads (resume PDFs, company logos) use FormData with Content-Type: multipart/form-data.
Register / Login
β
βΌ
POST /api/auth/register or /api/auth/login
β
βΌ
Response: { accessToken, refreshToken, userId, name, role }
β
βΌ
Store in localStorage + populate useAuthStore
β
βΌ
Router redirects to role-specific dashboard
(/employer/dashboard or /candidate/dashboard)
β
βΌ
All subsequent API requests auto-attach Bearer token (interceptor)
β
βΌ
Logout β POST /api/auth/logout β clear localStorage β redirect to /login
Route guard logic (router/index.ts):
requiresAuthβ redirect unauthenticated users to/loginguestβ redirect authenticated users away from login/register to their dashboardmeta.roleβ redirect users who don't match the required role to their own dashboard
Reusable UI components live in src/components/ui/ and are built on Reka UI (headless, accessible primitives) styled with Tailwind CSS.
| Component | Description |
|---|---|
Button |
Variants: default, outline, ghost, destructive; sizes: sm, md, lg |
Input |
Styled text input |
Label |
Form field label |
Badge |
Status pills (colors map to job / application / pipeline status) |
Card |
Container with CardHeader, CardTitle, CardDescription, CardContent, CardFooter |
Dialog |
Modal dialog with overlay |
AlertDialog |
Confirmation dialog for destructive actions |
DropdownMenu |
Contextual action menus |
Select |
Accessible select input |
Form |
Vee-Validate integrated form wrapper |
Progress |
Progress bar (used in hiring stage visualization) |
Avatar |
User/company avatar with fallback initials |
Separator |
Horizontal/vertical divider |
Sheet |
Side drawer panel |
Skeleton |
Loading placeholder |
Sonner (Toaster) |
Toast notifications β toast.success() / toast.error() |
Sidebar family |
Sidebar, SidebarContent, SidebarMenu, SidebarMenuItem, etc. |
Table family |
Data table primitives |
Class merging utility:
import { cn } from '@/lib/utils'
// Merges Tailwind classes and resolves conflicts
cn('px-4 py-2', isActive && 'bg-blue-500', className)Defined via CSS custom properties in src/assets/main.css.
| Token | Usage |
|---|---|
--color-primary |
Deep Professional Blue β primary actions, CTAs |
--color-primary-container |
Light primary background for cards/containers |
--color-tertiary |
Emerald β success states, positive indicators |
--color-surface |
Page background |
--color-surface-container |
Card/panel background |
--color-on-surface |
Primary text |
--color-on-surface-variant |
Secondary/muted text |
--color-destructive |
Error and destructive action red |
Dark mode is applied via the .dark class on <html>, toggled by ModeToggle.vue.
| Font | Role | Tailwind Class |
|---|---|---|
| Manrope | Headings, display text | font-headline |
| Inter | Body text, UI labels | font-body |
Both fonts are loaded from Google Fonts in index.html.
| Class | Effect |
|---|---|
.editorial-shadow |
Subtle blue-tinted box shadow |
.glass-surface |
Glassmorphism background blur |
.gradient-cta |
Primary β primary-container gradient |
Framework: Vitest 4.1.0 with JSDOM environment Component testing: Vue Test Utils 2.4.6 Coverage: v8
# Run all unit tests
npm run test:unit
# Generate coverage report
npm run test:covTest files follow the pattern src/**/__tests__/**/*.spec.ts.
Configuration is in vitest.config.ts, which extends vite.config.ts so all path aliases and plugins apply in tests as well.
A Dockerfile is included for containerized deployment. The production build is served by Nginx using nginx.conf, which handles SPA routing (all unmatched paths fall back to index.html).
# Build the image
docker build -t hireflow-frontend .
# Run the container
docker run -p 80:80 hireflow-frontendThe frontend container is included in the root docker-compose.yml alongside the backend services.