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:
@@ -27,6 +27,25 @@ from app.utils import check_rate_limit
|
|||||||
def register_routes(app, mail):
|
def register_routes(app, mail):
|
||||||
"""Register all application routes"""
|
"""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('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
"""Redirect to apply page"""
|
"""Redirect to apply page"""
|
||||||
@@ -91,6 +110,11 @@ def register_routes(app, mail):
|
|||||||
@app.route('/apply/<session_id>/personal')
|
@app.route('/apply/<session_id>/personal')
|
||||||
def page2_personal(session_id):
|
def page2_personal(session_id):
|
||||||
"""Page 2: Basic personal information"""
|
"""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)
|
app_data = load_application_data(session_id)
|
||||||
if not app_data:
|
if not app_data:
|
||||||
flash('Bewerbung nicht gefunden.', 'error')
|
flash('Bewerbung nicht gefunden.', 'error')
|
||||||
@@ -99,6 +123,7 @@ def register_routes(app, mail):
|
|||||||
return render_template('page2_personal.html',
|
return render_template('page2_personal.html',
|
||||||
session_id=session_id,
|
session_id=session_id,
|
||||||
job_name=app_data['job_name'],
|
job_name=app_data['job_name'],
|
||||||
|
current_page=app_data.get('current_page', 2),
|
||||||
data=app_data.get('personal_info', {}))
|
data=app_data.get('personal_info', {}))
|
||||||
|
|
||||||
|
|
||||||
@@ -180,6 +205,16 @@ def register_routes(app, mail):
|
|||||||
@app.route('/apply/<session_id>/motivation')
|
@app.route('/apply/<session_id>/motivation')
|
||||||
def page3_motivation(session_id):
|
def page3_motivation(session_id):
|
||||||
"""Page 3: Motivation and qualification questions"""
|
"""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)
|
app_data = load_application_data(session_id)
|
||||||
if not app_data:
|
if not app_data:
|
||||||
flash('Bewerbung nicht gefunden.', 'error')
|
flash('Bewerbung nicht gefunden.', 'error')
|
||||||
@@ -188,6 +223,7 @@ def register_routes(app, mail):
|
|||||||
return render_template('page3_motivation.html',
|
return render_template('page3_motivation.html',
|
||||||
session_id=session_id,
|
session_id=session_id,
|
||||||
job_name=app_data['job_name'],
|
job_name=app_data['job_name'],
|
||||||
|
current_page=app_data.get('current_page', 3),
|
||||||
data=app_data.get('motivation_answers', {}))
|
data=app_data.get('motivation_answers', {}))
|
||||||
|
|
||||||
|
|
||||||
@@ -249,6 +285,18 @@ def register_routes(app, mail):
|
|||||||
@app.route('/apply/<session_id>/upload')
|
@app.route('/apply/<session_id>/upload')
|
||||||
def page4_upload(session_id):
|
def page4_upload(session_id):
|
||||||
"""Page 4: Document upload"""
|
"""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)
|
app_data = load_application_data(session_id)
|
||||||
if not app_data:
|
if not app_data:
|
||||||
flash('Bewerbung nicht gefunden.', 'error')
|
flash('Bewerbung nicht gefunden.', 'error')
|
||||||
@@ -257,6 +305,7 @@ def register_routes(app, mail):
|
|||||||
return render_template('page4_upload.html',
|
return render_template('page4_upload.html',
|
||||||
session_id=session_id,
|
session_id=session_id,
|
||||||
job_name=app_data['job_name'],
|
job_name=app_data['job_name'],
|
||||||
|
current_page=app_data.get('current_page', 4),
|
||||||
uploaded_files=app_data.get('uploaded_files', []),
|
uploaded_files=app_data.get('uploaded_files', []),
|
||||||
max_files=app.config['MAX_FILES'])
|
max_files=app.config['MAX_FILES'])
|
||||||
|
|
||||||
@@ -395,6 +444,20 @@ def register_routes(app, mail):
|
|||||||
@app.route('/apply/<session_id>/confirmation')
|
@app.route('/apply/<session_id>/confirmation')
|
||||||
def page5_confirmation(session_id):
|
def page5_confirmation(session_id):
|
||||||
"""Page 5: Confirmation page"""
|
"""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)
|
app_data = load_application_data(session_id)
|
||||||
if not app_data:
|
if not app_data:
|
||||||
flash('Bewerbung nicht gefunden.', 'error')
|
flash('Bewerbung nicht gefunden.', 'error')
|
||||||
@@ -403,6 +466,7 @@ def register_routes(app, mail):
|
|||||||
return render_template('page5_confirmation.html',
|
return render_template('page5_confirmation.html',
|
||||||
session_id=session_id,
|
session_id=session_id,
|
||||||
job_name=app_data['job_name'],
|
job_name=app_data['job_name'],
|
||||||
|
current_page=app_data.get('current_page', 5),
|
||||||
email=app_data['email'])
|
email=app_data['email'])
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,208 @@
|
|||||||
|
<objective>
|
||||||
|
Add a navigation bar with progress indicator to the multi-page job application workflow, allowing users to navigate backward to previous pages to make changes while maintaining sequential forward progression.
|
||||||
|
|
||||||
|
This will improve user experience by allowing applicants to review and edit their information without losing progress, reducing form abandonment and improving data quality.
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
Current state:
|
||||||
|
- 5-page workflow: email capture → personal info → motivation → upload → confirmation
|
||||||
|
- Users can only move forward through pages
|
||||||
|
- No way to go back and edit previous pages after submission
|
||||||
|
- Pages are: page1_email.html, page2_personal.html, page3_motivation.html, page4_upload.html, page5_confirmation.html
|
||||||
|
- Uses session-based application tracking with session_id
|
||||||
|
- All data persisted to YAML files in app/models.py
|
||||||
|
|
||||||
|
Target state:
|
||||||
|
- Progress indicator showing all 5 steps
|
||||||
|
- Users can click on completed steps (pages they've already visited) to go back
|
||||||
|
- Cannot skip ahead to pages they haven't reached yet
|
||||||
|
- Current page highlighted in progress bar
|
||||||
|
- Navigation preserves all previously entered data
|
||||||
|
|
||||||
|
Review the current implementation:
|
||||||
|
@app/routes.py
|
||||||
|
@templates/page2_personal.html
|
||||||
|
@templates/page3_motivation.html
|
||||||
|
@templates/page4_upload.html
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<requirements>
|
||||||
|
1. **Create navigation component**:
|
||||||
|
- Add progress bar/breadcrumb navigation to base.html or as includable partial
|
||||||
|
- Show all 5 steps: Email → Personal → Motivation → Upload → Confirmation
|
||||||
|
- Visually distinguish between: completed (clickable), current (highlighted), future (disabled)
|
||||||
|
- Must be present on pages 2, 3, 4, 5 (not on page 1)
|
||||||
|
|
||||||
|
2. **Navigation behavior**:
|
||||||
|
- **Backward navigation**: Users can click on any previously completed page to go back
|
||||||
|
- **Forward navigation**: Sequential only - users must click "Next" buttons, cannot skip ahead
|
||||||
|
- **Current page tracking**: Use `current_page` field in application data to determine which pages are accessible
|
||||||
|
- **Data preservation**: All previously entered data must be preserved when navigating
|
||||||
|
|
||||||
|
3. **Visual design** (German labels):
|
||||||
|
- Step 1: "E-Mail" (page1_email.html)
|
||||||
|
- Step 2: "Daten" (page2_personal.html)
|
||||||
|
- Step 3: "Motivation" (page3_motivation.html)
|
||||||
|
- Step 4: "Dokumente" (page4_upload.html)
|
||||||
|
- Step 5: "Bestätigung" (page5_confirmation.html)
|
||||||
|
|
||||||
|
- Completed steps: Green checkmark + clickable link
|
||||||
|
- Current step: Bold + highlighted background
|
||||||
|
- Future steps: Gray + no link
|
||||||
|
|
||||||
|
4. **Implementation approach**:
|
||||||
|
- Add navigation partial to templates/ (e.g., `_navigation.html` or add to `base.html`)
|
||||||
|
- Pass `current_page` and `session_id` to all templates
|
||||||
|
- Add CSS styling for progress bar (can be inline in base.html or in style.css)
|
||||||
|
- Update route handlers to accept navigation from any completed page
|
||||||
|
- Ensure CSRF tokens work with navigation
|
||||||
|
|
||||||
|
5. **Data handling**:
|
||||||
|
- When user navigates backward, load existing data from YAML
|
||||||
|
- Pre-populate forms with saved data
|
||||||
|
- When user submits, update data and allow forward navigation
|
||||||
|
- Don't lose uploaded files when navigating
|
||||||
|
|
||||||
|
6. **Edge cases**:
|
||||||
|
- If user tries to access a page beyond current_page directly via URL, redirect to current page
|
||||||
|
- If session_id is invalid, redirect to page 1
|
||||||
|
- On page 5 (confirmation), navigation can be read-only (all steps completed)
|
||||||
|
</requirements>
|
||||||
|
|
||||||
|
<implementation>
|
||||||
|
**Suggested implementation strategy**:
|
||||||
|
|
||||||
|
1. **Create navigation component**:
|
||||||
|
- Create `templates/_navigation.html` partial with Jinja2 template for progress bar
|
||||||
|
- OR add navigation directly to `base.html` as a conditional block
|
||||||
|
- Use Bootstrap-style progress steps or custom CSS
|
||||||
|
|
||||||
|
2. **Update base.html**:
|
||||||
|
- Add navigation include or block after header
|
||||||
|
- Only show navigation if `session_id` exists and `current_page >= 2`
|
||||||
|
|
||||||
|
3. **Update all page templates** (page2, page3, page4, page5):
|
||||||
|
- Include navigation component at top
|
||||||
|
- Pass `current_page`, `session_id`, `job_name` to navigation
|
||||||
|
|
||||||
|
4. **Update route handlers** in `app/routes.py`:
|
||||||
|
- Add helper function to validate page access: `can_access_page(session_id, page_number)`
|
||||||
|
- Add navigation routes or update existing routes to handle backward navigation
|
||||||
|
- Ensure all routes check if user can access the requested page
|
||||||
|
|
||||||
|
5. **CSS styling**:
|
||||||
|
- Add styles to `static/style.css` for progress bar
|
||||||
|
- Use semantic colors: green for completed, blue/highlight for current, gray for future
|
||||||
|
- Make clickable steps have hover effects
|
||||||
|
|
||||||
|
**Example navigation structure** (German):
|
||||||
|
```
|
||||||
|
[✓ E-Mail] → [✓ Daten] → [● Motivation] → [ Dokumente] → [ Bestätigung]
|
||||||
|
clickable clickable current disabled disabled
|
||||||
|
```
|
||||||
|
|
||||||
|
**WHY this approach**:
|
||||||
|
- Progress indicator provides clear visual feedback on completion status
|
||||||
|
- Backward navigation allows data correction without starting over
|
||||||
|
- Sequential forward validation ensures data quality and prevents skipping required fields
|
||||||
|
- Session-based tracking already in place, just need to leverage current_page
|
||||||
|
- Preserves all existing functionality while adding navigation
|
||||||
|
</implementation>
|
||||||
|
|
||||||
|
<validation_logic>
|
||||||
|
Add helper function to check page access:
|
||||||
|
|
||||||
|
```python
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
Use in route handlers:
|
||||||
|
```python
|
||||||
|
@app.route('/apply/<session_id>/personal')
|
||||||
|
def page2_personal(session_id):
|
||||||
|
if not can_access_page(session_id, 2):
|
||||||
|
return redirect(url_for('page1_email'))
|
||||||
|
# ... rest of handler
|
||||||
|
```
|
||||||
|
</validation_logic>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
Modify the following files:
|
||||||
|
|
||||||
|
- `./templates/base.html` - Add navigation component or include statement
|
||||||
|
- `./templates/_navigation.html` (NEW) - Progress bar component (if using partial approach)
|
||||||
|
- `./templates/page2_personal.html` - Ensure navigation is displayed
|
||||||
|
- `./templates/page3_motivation.html` - Ensure navigation is displayed
|
||||||
|
- `./templates/page4_upload.html` - Ensure navigation is displayed
|
||||||
|
- `./templates/page5_confirmation.html` - Ensure navigation is displayed (read-only mode)
|
||||||
|
- `./app/routes.py` - Add `can_access_page()` helper and update route guards
|
||||||
|
- `./static/style.css` - Add navigation styling
|
||||||
|
|
||||||
|
Do NOT modify:
|
||||||
|
- Data models or storage logic
|
||||||
|
- Form validation logic
|
||||||
|
- Email functionality
|
||||||
|
- File upload logic
|
||||||
|
</output>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
Before declaring complete, verify:
|
||||||
|
|
||||||
|
1. **Visual verification**:
|
||||||
|
- Progress bar appears on pages 2, 3, 4, 5
|
||||||
|
- Current step is highlighted
|
||||||
|
- Completed steps show checkmarks and are clickable
|
||||||
|
- Future steps are grayed out and not clickable
|
||||||
|
|
||||||
|
2. **Navigation testing**:
|
||||||
|
- From page 3, click on "Daten" → should go to page 2 with data pre-populated
|
||||||
|
- From page 4, click on "E-Mail" → should go to page 1 with email shown
|
||||||
|
- Try to access page 4 by typing URL when on page 2 → should redirect to page 2
|
||||||
|
- Complete workflow and verify navigation works at each step
|
||||||
|
|
||||||
|
3. **Data preservation**:
|
||||||
|
- Fill out page 2, go to page 3, go back to page 2 → all fields still filled
|
||||||
|
- Upload files on page 4, go back to page 3, return to page 4 → files still there
|
||||||
|
- Make changes on a previous page and save → changes persisted
|
||||||
|
|
||||||
|
4. **Edge cases**:
|
||||||
|
- Invalid session_id → redirect to page 1
|
||||||
|
- Accessing page directly via URL beyond current_page → redirect to current page
|
||||||
|
- Page 5 navigation is read-only (all steps completed)
|
||||||
|
|
||||||
|
5. **Tests still pass**:
|
||||||
|
- Run `pytest tests/ -v` to ensure no regressions
|
||||||
|
- Existing tests should pass (they test form submission, not navigation)
|
||||||
|
- Consider adding new tests for navigation behavior (optional)
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- Progress bar/breadcrumb navigation visible on pages 2-5
|
||||||
|
- Users can navigate backward to any completed page
|
||||||
|
- Users cannot skip ahead to uncompleted pages
|
||||||
|
- All previously entered data is preserved when navigating
|
||||||
|
- Current page is visually distinct in navigation
|
||||||
|
- Navigation uses German labels matching the application
|
||||||
|
- All existing functionality still works
|
||||||
|
- No test regressions
|
||||||
|
- Clean, professional styling that matches existing design
|
||||||
|
</success_criteria>
|
||||||
|
Completed: Sa 27 Dez 2025 23:11:04 CET
|
||||||
@@ -366,6 +366,123 @@ header h1 {
|
|||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Progress Navigation */
|
||||||
|
.progress-navigation {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-steps {
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
counter-reset: step;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-step {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-step:not(:last-child)::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
left: 50%;
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
background-color: #ddd;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-step.completed:not(:last-child)::after {
|
||||||
|
background-color: #28a745;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-link {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #666;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-step.completed .progress-link {
|
||||||
|
color: #28a745;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-step.active .progress-link {
|
||||||
|
color: #007bff;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-step.disabled .progress-link {
|
||||||
|
color: #999;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-icon {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.1em;
|
||||||
|
background-color: white;
|
||||||
|
border: 2px solid #ddd;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-step.completed .progress-icon {
|
||||||
|
background-color: #28a745;
|
||||||
|
border-color: #28a745;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-step.active .progress-icon {
|
||||||
|
background-color: #007bff;
|
||||||
|
border-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-step.disabled .progress-icon {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-color: #ddd;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-step.completed a.progress-link:hover .progress-icon {
|
||||||
|
transform: scale(1.1);
|
||||||
|
box-shadow: 0 2px 8px rgba(40, 167, 69, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-step.completed a.progress-link:hover {
|
||||||
|
color: #1e7e34;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-label {
|
||||||
|
font-size: 0.85em;
|
||||||
|
display: block;
|
||||||
|
max-width: 100px;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-step.active .progress-label {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
/* Footer */
|
/* Footer */
|
||||||
footer {
|
footer {
|
||||||
margin-top: 40px;
|
margin-top: 40px;
|
||||||
@@ -411,4 +528,33 @@ footer {
|
|||||||
.page-intro h2 {
|
.page-intro h2 {
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Progress Navigation Mobile */
|
||||||
|
.progress-navigation {
|
||||||
|
padding: 15px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-steps {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-step {
|
||||||
|
flex: 0 0 calc(50% - 8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-step:not(:last-child)::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-icon {
|
||||||
|
width: 35px;
|
||||||
|
height: 35px;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-label {
|
||||||
|
font-size: 0.75em;
|
||||||
|
max-width: 80px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
{% if session_id and current_page and current_page >= 2 %}
|
||||||
|
<nav class="progress-navigation" aria-label="Bewerbungsfortschritt">
|
||||||
|
<ol class="progress-steps">
|
||||||
|
<!-- Step 1: Email -->
|
||||||
|
<li class="progress-step {% if current_page == 1 %}active{% elif current_page > 1 %}completed{% else %}disabled{% endif %}">
|
||||||
|
{% if current_page >= 1 %}
|
||||||
|
<a href="{{ url_for('page1_email') }}" class="progress-link">
|
||||||
|
<span class="progress-icon">{% if current_page > 1 %}✓{% else %}1{% endif %}</span>
|
||||||
|
<span class="progress-label">E-Mail</span>
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<span class="progress-link">
|
||||||
|
<span class="progress-icon">1</span>
|
||||||
|
<span class="progress-label">E-Mail</span>
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Step 2: Personal Info -->
|
||||||
|
<li class="progress-step {% if current_page == 2 %}active{% elif current_page > 2 %}completed{% else %}disabled{% endif %}">
|
||||||
|
{% if current_page >= 2 %}
|
||||||
|
<a href="{{ url_for('page2_personal', session_id=session_id) }}" class="progress-link">
|
||||||
|
<span class="progress-icon">{% if current_page > 2 %}✓{% else %}2{% endif %}</span>
|
||||||
|
<span class="progress-label">Daten</span>
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<span class="progress-link">
|
||||||
|
<span class="progress-icon">2</span>
|
||||||
|
<span class="progress-label">Daten</span>
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Step 3: Motivation -->
|
||||||
|
<li class="progress-step {% if current_page == 3 %}active{% elif current_page > 3 %}completed{% else %}disabled{% endif %}">
|
||||||
|
{% if current_page >= 3 %}
|
||||||
|
<a href="{{ url_for('page3_motivation', session_id=session_id) }}" class="progress-link">
|
||||||
|
<span class="progress-icon">{% if current_page > 3 %}✓{% else %}3{% endif %}</span>
|
||||||
|
<span class="progress-label">Motivation</span>
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<span class="progress-link">
|
||||||
|
<span class="progress-icon">3</span>
|
||||||
|
<span class="progress-label">Motivation</span>
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Step 4: Upload -->
|
||||||
|
<li class="progress-step {% if current_page == 4 %}active{% elif current_page > 4 %}completed{% else %}disabled{% endif %}">
|
||||||
|
{% if current_page >= 4 %}
|
||||||
|
<a href="{{ url_for('page4_upload', session_id=session_id) }}" class="progress-link">
|
||||||
|
<span class="progress-icon">{% if current_page > 4 %}✓{% else %}4{% endif %}</span>
|
||||||
|
<span class="progress-label">Dokumente</span>
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<span class="progress-link">
|
||||||
|
<span class="progress-icon">4</span>
|
||||||
|
<span class="progress-label">Dokumente</span>
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Step 5: Confirmation -->
|
||||||
|
<li class="progress-step {% if current_page == 5 %}active{% elif current_page > 5 %}completed{% else %}disabled{% endif %}">
|
||||||
|
{% if current_page >= 5 %}
|
||||||
|
<a href="{{ url_for('page5_confirmation', session_id=session_id) }}" class="progress-link">
|
||||||
|
<span class="progress-icon">{% if current_page > 5 %}✓{% else %}5{% endif %}</span>
|
||||||
|
<span class="progress-label">Bestätigung</span>
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<span class="progress-link">
|
||||||
|
<span class="progress-icon">5</span>
|
||||||
|
<span class="progress-label">Bestätigung</span>
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
{% endif %}
|
||||||
@@ -12,6 +12,9 @@
|
|||||||
<h1>{% block header %}Bewerbungsformular{% endblock %}</h1>
|
<h1>{% block header %}Bewerbungsformular{% endblock %}</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<!-- Progress Navigation -->
|
||||||
|
{% include '_navigation.html' %}
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}Bewerbung - Persönliche Daten{% endblock %}
|
{% block title %}Bewerbung - Daten{% endblock %}
|
||||||
|
|
||||||
{% block header %}Bewerbung: {{ job_name }}{% endblock %}
|
{% block header %}Bewerbung: {{ job_name }}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="page-intro">
|
<div class="page-intro">
|
||||||
<h2>Schritt 1 von 3: Persönliche Daten</h2>
|
<h2>Schritt 1 von 3: Daten</h2>
|
||||||
<p>Bitte geben Sie Ihre persönlichen Informationen ein. Alle Felder mit * sind Pflichtfelder.</p>
|
<p>Bitte geben Sie Ihre persönlichen Informationen ein. Alle Felder mit * sind Pflichtfelder.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
<div class="page-intro">
|
<div class="page-intro">
|
||||||
<h2>Schritt 2 von 3: Motivation und Qualifikationen</h2>
|
<h2>Schritt 2 von 3: Motivation und Qualifikationen</h2>
|
||||||
<p>Bitte beantworten Sie die folgenden Fragen. Alle Angaben sind optional, helfen uns aber, Sie besser kennenzulernen.</p>
|
<p>Bitte beantworten Sie die folgenden Fragen. Alle Angaben sind optional, helfen uns aber, Sie besser kennenzulernen.</p>
|
||||||
|
<p>Falls sie ein Motivationsschreiben einreichen wollen haben Sie im nächsten Schritt die Möglichkeit dazu.</p>
|
||||||
<p><small>Maximal ca. 3000 Zeichen pro Antwort (etwa eine A4-Seite)</small></p>
|
<p><small>Maximal ca. 3000 Zeichen pro Antwort (etwa eine A4-Seite)</small></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user