feat: add navigation bar with progress indicator and backward navigation

- Created _navigation.html component with 5-step progress indicator
- Added German labels (E-Mail, Persönliche Daten, Motivation, Dokumente, Bestätigung)
- Implemented can_access_page() validation in routes.py
- Users can navigate backward to completed pages (data preserved)
- Sequential forward progression enforced (cannot skip ahead)
- Visual states: completed (green checkmark), current (blue), future (gray)
- Added comprehensive CSS styling with responsive design
- Updated base.html to include navigation component
- Updated page templates to pass current_page context
- Navigation appears on pages 2-5 only
- All 102 tests passing with no regressions
This commit is contained in:
2025-12-27 23:12:21 +01:00
parent c0cca71b74
commit ef5ccc18f7
7 changed files with 504 additions and 2 deletions
+64
View File
@@ -27,6 +27,25 @@ from app.utils import check_rate_limit
def register_routes(app, mail):
"""Register all application routes"""
def can_access_page(session_id, requested_page):
"""
Determine if user can access the requested page.
Rules:
- Can always access pages <= current_page (backward navigation)
- Cannot access pages > current_page (must progress sequentially)
- Page 1 is always accessible
"""
if requested_page == 1:
return True
app_data = load_application_data(session_id)
if not app_data:
return False
current_page = app_data.get('current_page', 1)
return requested_page <= current_page
@app.route('/')
def index():
"""Redirect to apply page"""
@@ -91,6 +110,11 @@ def register_routes(app, mail):
@app.route('/apply/<session_id>/personal')
def page2_personal(session_id):
"""Page 2: Basic personal information"""
# Validate page access
if not can_access_page(session_id, 2):
flash('Sie müssen zuerst die vorherigen Schritte abschließen.', 'error')
return redirect(url_for('page1_email'))
app_data = load_application_data(session_id)
if not app_data:
flash('Bewerbung nicht gefunden.', 'error')
@@ -99,6 +123,7 @@ def register_routes(app, mail):
return render_template('page2_personal.html',
session_id=session_id,
job_name=app_data['job_name'],
current_page=app_data.get('current_page', 2),
data=app_data.get('personal_info', {}))
@@ -180,6 +205,16 @@ def register_routes(app, mail):
@app.route('/apply/<session_id>/motivation')
def page3_motivation(session_id):
"""Page 3: Motivation and qualification questions"""
# Validate page access
if not can_access_page(session_id, 3):
app_data = load_application_data(session_id)
if app_data:
current_page = app_data.get('current_page', 2)
if current_page == 2:
return redirect(url_for('page2_personal', session_id=session_id))
flash('Sie müssen zuerst die vorherigen Schritte abschließen.', 'error')
return redirect(url_for('page1_email'))
app_data = load_application_data(session_id)
if not app_data:
flash('Bewerbung nicht gefunden.', 'error')
@@ -188,6 +223,7 @@ def register_routes(app, mail):
return render_template('page3_motivation.html',
session_id=session_id,
job_name=app_data['job_name'],
current_page=app_data.get('current_page', 3),
data=app_data.get('motivation_answers', {}))
@@ -249,6 +285,18 @@ def register_routes(app, mail):
@app.route('/apply/<session_id>/upload')
def page4_upload(session_id):
"""Page 4: Document upload"""
# Validate page access
if not can_access_page(session_id, 4):
app_data = load_application_data(session_id)
if app_data:
current_page = app_data.get('current_page', 2)
if current_page == 2:
return redirect(url_for('page2_personal', session_id=session_id))
elif current_page == 3:
return redirect(url_for('page3_motivation', session_id=session_id))
flash('Sie müssen zuerst die vorherigen Schritte abschließen.', 'error')
return redirect(url_for('page1_email'))
app_data = load_application_data(session_id)
if not app_data:
flash('Bewerbung nicht gefunden.', 'error')
@@ -257,6 +305,7 @@ def register_routes(app, mail):
return render_template('page4_upload.html',
session_id=session_id,
job_name=app_data['job_name'],
current_page=app_data.get('current_page', 4),
uploaded_files=app_data.get('uploaded_files', []),
max_files=app.config['MAX_FILES'])
@@ -395,6 +444,20 @@ def register_routes(app, mail):
@app.route('/apply/<session_id>/confirmation')
def page5_confirmation(session_id):
"""Page 5: Confirmation page"""
# Validate page access
if not can_access_page(session_id, 5):
app_data = load_application_data(session_id)
if app_data:
current_page = app_data.get('current_page', 2)
if current_page == 2:
return redirect(url_for('page2_personal', session_id=session_id))
elif current_page == 3:
return redirect(url_for('page3_motivation', session_id=session_id))
elif current_page == 4:
return redirect(url_for('page4_upload', session_id=session_id))
flash('Sie müssen zuerst die vorherigen Schritte abschließen.', 'error')
return redirect(url_for('page1_email'))
app_data = load_application_data(session_id)
if not app_data:
flash('Bewerbung nicht gefunden.', 'error')
@@ -403,6 +466,7 @@ def register_routes(app, mail):
return render_template('page5_confirmation.html',
session_id=session_id,
job_name=app_data['job_name'],
current_page=app_data.get('current_page', 5),
email=app_data['email'])