Duration: 2 weeks (Oct 16 - Nov 1, 2025) Goal: Production-ready UI with all critical issues fixed
Goal: Fix all SCSS budget issues (CRITICAL - blocking production build)
Task 1.1: Optimize dashboard.scss (1 hour)
Steps:
@import '../../../styles/shared/buttons';
@import '../../../styles/shared/cards';
Remove duplicate button styles (lines 150-250)
Remove duplicate card styles (lines 50-100)
Extract chart styles to separate file:
_charts.scss in shared folderOptimize stat-card styles:
Test component still renders correctly
Task 1.2: Optimize application-form.scss (1.5 hours)
Steps:
@import '../../../styles/shared/forms';
@import '../../../styles/shared/buttons';
Remove duplicate form-group styles (lines 20-80)
Remove duplicate button styles (lines 300-400)
Extract file upload styles to shared:
_file-upload.scssOptimize validation error styles
Combine media queries
Test form validation still works
Task 1.3: Optimize job-detail.component.scss (1 hour)
Steps:
Task 1.4: Optimize register.scss and job-list.scss (30 min each)
register.scss:
job-list.scss:
Task 1.5: Create additional shared styles (1 hour)
Files to create:
src/styles/shared/
_charts.scss (for dashboard charts)
_file-upload.scss (for file uploads)
_filters.scss (for filter panels)
_job-meta.scss (for job metadata)
_modals.scss (for modal dialogs)
Task 1.6: Update angular.json for SCSS compression (30 min)
{
"styles": {
"input": "src/styles.scss",
"bundleName": "styles",
"inject": true
},
"stylePreprocessorOptions": {
"includePaths": ["src/styles/shared"]
}
}
Task 1.7: Build and verify (1 hour)
cd frontend/recruitment-portal
npm run build
Expected output:
✅ Compilation: SUCCESS
✅ SCSS Budgets: 0 warnings
✅ TypeScript: No errors
Task 1.8: Visual regression testing (1.5 hours)
Goal: Add CV upload functionality to homepage
Task 2.1: Create cv-upload-button component (1.5 hours)
Generate component:
cd frontend/recruitment-portal/src/app/components/shared
ng generate component cv-upload-button --standalone
cv-upload-button.component.ts:
import { Component, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AuthService } from '../../../services/auth.service';
import { Router } from '@angular/router';
@Component({
selector: 'app-cv-upload-button',
standalone: true,
imports: [CommonModule],
templateUrl: './cv-upload-button.component.html',
styleUrl: './cv-upload-button.component.scss'
})
export class CvUploadButtonComponent {
private authService = inject(AuthService);
private router = inject(Router);
onUploadClick(): void {
if (this.authService.isAuthenticated()) {
// Open CV upload modal
// TODO: Implement modal
} else {
// Redirect to register with return URL
this.router.navigate(['/register'], {
queryParams: { returnUrl: '/profile', action: 'upload-cv' }
});
}
}
}
cv-upload-button.component.html:
<button
class="cv-upload-btn"
(click)="onUploadClick()"
type="button">
<svg class="upload-icon" width="20" height="20" viewBox="0 0 20 20">
<path d="M10 0L6 4h3v8h2V4h3L10 0z"/>
<path d="M2 14h16v2H2v-2z"/>
</svg>
<span>Upload CV</span>
</button>
cv-upload-button.component.scss:
.cv-upload-btn {
display: inline-flex;
align-items: center;
gap: 0.75rem;
padding: 0.875rem 1.5rem;
background: linear-gradient(135deg, #0d6efd 0%, #0a58ca 100%);
color: white;
border: none;
border-radius: 8px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
box-shadow: 0 4px 12px rgba(13, 110, 253, 0.3);
transition: all 0.3s ease;
&:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(13, 110, 253, 0.4);
}
.upload-icon {
flex-shrink: 0;
fill: currentColor;
}
}
Task 2.2: Create cv-upload-modal component (2 hours)
Generate component:
ng generate component cv-upload-modal --standalone
cv-upload-modal.component.ts:
import { Component, EventEmitter, Output, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app-cv-upload-modal',
standalone: true,
imports: [CommonModule, ReactiveFormsModule],
templateUrl: './cv-upload-modal.component.html',
styleUrl: './cv-upload-modal.component.scss'
})
export class CvUploadModalComponent {
@Output() close = new EventEmitter<void>();
@Output() uploaded = new EventEmitter<void>();
private fb = inject(FormBuilder);
private http = inject(HttpClient);
uploadForm = this.fb.group({
file: [null, Validators.required],
name: ['', Validators.required],
email: ['', [Validators.required, Validators.email]],
phone: ['']
});
selectedFile: File | null = null;
uploading = false;
uploadProgress = 0;
onFileSelected(event: any): void {
const file = event.target.files[0];
if (file && this.isValidFile(file)) {
this.selectedFile = file;
this.uploadForm.patchValue({ file });
}
}
isValidFile(file: File): boolean {
const validTypes = ['application/pdf', 'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'];
const maxSize = 5 * 1024 * 1024; // 5MB
return validTypes.includes(file.type) && file.size <= maxSize;
}
onSubmit(): void {
if (this.uploadForm.valid && this.selectedFile) {
this.uploading = true;
const formData = new FormData();
formData.append('cv', this.selectedFile);
formData.append('name', this.uploadForm.value.name!);
formData.append('email', this.uploadForm.value.email!);
if (this.uploadForm.value.phone) {
formData.append('phone', this.uploadForm.value.phone);
}
// TODO: Replace with actual API endpoint
this.http.post('/api/cv/upload', formData, {
reportProgress: true,
observe: 'events'
}).subscribe({
next: (event: any) => {
if (event.type === 1) { // UploadProgress
this.uploadProgress = Math.round(100 * event.loaded / event.total);
} else if (event.type === 4) { // Response
this.uploaded.emit();
this.onClose();
}
},
error: (error) => {
console.error('Upload failed:', error);
this.uploading = false;
}
});
}
}
onClose(): void {
this.close.emit();
}
}
Task 2.3: Integrate with job-list component (30 min)
job-list.component.html (add near search bar):
<div class="header-actions">
<div class="search-bar">
<!-- Existing search -->
</div>
<app-cv-upload-button></app-cv-upload-button>
</div>
<!-- Add modal -->
<app-cv-upload-modal
*ngIf="showCvModal"
(close)="showCvModal = false"
(uploaded)="onCvUploaded()">
</app-cv-upload-modal>
job-list.component.ts:
// Add imports
import { CvUploadButtonComponent } from '../shared/cv-upload-button/cv-upload-button.component';
import { CvUploadModalComponent } from '../shared/cv-upload-modal/cv-upload-modal.component';
// Add to imports array
imports: [
// ... existing imports
CvUploadButtonComponent,
CvUploadModalComponent
]
// Add property
showCvModal = false;
// Add method
onCvUploaded(): void {
this.toastService.show('CV succesvol geüpload!', 'success');
}
Task 2.4: Style CV upload modal (1 hour)
Task 2.5: Add validation and error handling (1 hour)
Task 2.6: Testing (2 hours)
Goal: Comprehensive testing of all Sprint 4 Week 1 work
Task 3.1: Build verification (1 hour)
# Clean build
npm run build
# Check bundle sizes
# Verify no warnings
# Test production build locally
Task 3.2: Cross-browser testing (2 hours)
Browsers to test:
Test checklist per browser:
Task 3.3: Mobile device testing (1 hour)
Devices:
Test:
Task 3.4: Regression testing (2 hours)
Test all major flows:
Task 3.5: Performance testing (1 hour)
Task 3.6: Documentation updates (1 hour)
Goal: Create comprehensive admin navigation component
Task 4.1: Create admin-nav component (2 hours)
Generate:
ng generate component components/admin/admin-nav --standalone
admin-nav.component.ts:
import { Component, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule, Router } from '@angular/router';
import { AuthService } from '../../../services/auth.service';
interface NavItem {
label: string;
route: string;
icon: string;
}
@Component({
selector: 'app-admin-nav',
standalone: true,
imports: [CommonModule, RouterModule],
templateUrl: './admin-nav.component.html',
styleUrl: './admin-nav.component.scss'
})
export class AdminNavComponent {
private authService = inject(AuthService);
private router = inject(Router);
menuOpen = false;
navItems: NavItem[] = [
{ label: 'Dashboard', route: '/admin/dashboard', icon: '📊' },
{ label: 'Gebruikers', route: '/admin/users', icon: '👥' },
{ label: 'Vacatures', route: '/admin/jobs', icon: '💼' },
{ label: 'Sollicitaties', route: '/admin/applications', icon: '📝' }
];
get currentUser() {
return this.authService.currentUser();
}
toggleMenu(): void {
this.menuOpen = !this.menuOpen;
}
logout(): void {
this.authService.logout();
this.router.navigate(['/login']);
}
}
Task 4.2: Add breadcrumb navigation (1 hour)
Create breadcrumb service:
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
export interface Breadcrumb {
label: string;
url: string;
}
@Injectable({ providedIn: 'root' })
export class BreadcrumbService {
private breadcrumbsSubject = new BehaviorSubject<Breadcrumb[]>([]);
breadcrumbs$ = this.breadcrumbsSubject.asObservable();
setBreadcrumbs(breadcrumbs: Breadcrumb[]): void {
this.breadcrumbsSubject.next(breadcrumbs);
}
}
Task 4.3: Style admin navigation (1 hour)
Task 4.4: Integrate with all admin pages (2 hours)
Task 4.5: Mobile responsive admin nav (1 hour)
Task 4.6: Testing (1 hour)
Goal: Fix all mobile responsive issues
Task 5.1: Admin Dashboard Mobile (2 hours)
Task 5.2: Job Listings Mobile (2 hours)
Task 5.3: Forms Mobile (2 hours)
Task 5.4: Modal Dialogs Mobile (1 hour)
Task 5.5: Final Mobile Testing (1 hour)
Goal: WCAG 2.1 AA compliance
Task 6.1: Alt text and labels (2 hours)
Task 6.2: Keyboard navigation (2 hours)
Task 6.3: Color contrast (2 hours)
Task 6.4: Screen reader testing (2 hours)
Goal: Optimize performance
Task 7.1: Bundle size optimization (3 hours)
Task 7.2: Image optimization (2 hours)
Task 7.3: Caching strategy (2 hours)
Task 7.4: Performance testing (1 hour)
Goal: Complete Sprint 4
Task 8.1: Final bug fixes (2 hours)
Task 8.2: Final cross-browser testing (2 hours)
Task 8.3: Documentation (3 hours)
Update:
Task 8.4: Sprint 4 retrospective (1 hour)
Sprint 4 is complete when:
Estimated Total Hours: 80 hours (2 weeks × 40 hours) Actual Timeline: Oct 16 - Nov 1, 2025 Next: Sprint 5 (Knowledge Base) starts Nov 4, 2025
Reacties