Implement product selection landing page (Feature 002)

Adds landing page at root URL (/) that displays all active products with links to feedback submission forms. This replaces the requirement for users to know direct product URLs.

Changes:
- Added Product.load_active() method to filter and sort active products alphabetically
- Created landing route blueprint with error handling and structured logging
- Registered landing blueprint in app factory, replacing old index route
- Created landing page template with product list and empty state
- Added comprehensive contract tests (6 tests) covering active products, filtering, sorting, XSS prevention
- Added integration test for complete user flow from landing page to submission form

All 7 tests pass. User Story 1 (P1 - MVP) complete.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-17 14:54:51 +02:00
co-authored by Claude
parent 8640d803a6
commit f7f225ad09
7 changed files with 331 additions and 32 deletions
+2 -8
View File
@@ -182,18 +182,12 @@ def create_app(config_name='development'):
)
# Register blueprints
from app.routes import submission, dashboard, admin, auth
from app.routes import submission, dashboard, admin, auth, landing
app.register_blueprint(submission.bp)
app.register_blueprint(dashboard.bp)
app.register_blueprint(admin.bp)
app.register_blueprint(auth.bp)
# Set index route
@app.route('/')
def index():
"""Welcome page"""
from flask import render_template
return render_template('index.html')
app.register_blueprint(landing.landing_bp) # Landing page (product selection)
# Health check endpoint (T208)
@app.route('/health')
+16
View File
@@ -159,6 +159,22 @@ class Product:
return products
@classmethod
def load_active(cls):
"""Load all active products, sorted alphabetically by name then product_id
Returns:
list[Product]: Active products with valid submission_url_slug, sorted by:
1. name (case-insensitive alphabetical)
2. product_id (alphabetical) as tiebreaker
Products with missing/invalid submission_url_slug are excluded.
"""
all_products = cls.get_all()
active = [p for p in all_products
if p.status == 'active' and p.submission_url_slug]
return sorted(active, key=lambda p: (p.name.lower(), p.product_id))
def save(self):
"""Save product to filesystem"""
product_dir = self._get_product_dir(self.product_id)
+29
View File
@@ -0,0 +1,29 @@
"""Landing page route - product selection"""
from flask import Blueprint, render_template, current_app
from app.models.product import Product
landing_bp = Blueprint('landing', __name__)
@landing_bp.route('/')
def index():
"""Landing page showing all active products for feedback submission
Returns:
Rendered HTML template with:
- List of active products (if any)
- Empty state message (if no active products)
"""
try:
products = Product.load_active()
current_app.logger.info(
f'Landing page accessed: {len(products)} active products available'
)
return render_template('landing/index.html', products=products)
except Exception as e:
current_app.logger.error(
f'Error loading landing page: {e}',
exc_info=True
)
# Graceful degradation - show empty product list
return render_template('landing/index.html', products=[])
+37
View File
@@ -0,0 +1,37 @@
{% extends "base.html" %}
{% block title %}Select Product - Reklamator{% endblock %}
{% block content %}
<h1>Submit Feedback</h1>
{% if products %}
<p>Select a product to share your feedback, report issues, or suggest improvements.</p>
<div style="margin-top: 30px;">
{% for product in products %}
<div style="background-color: #f8f9fa; padding: 20px; border-radius: 4px; margin-bottom: 15px; border-left: 4px solid #3498db;">
<h2 style="margin-top: 0; margin-bottom: 10px; font-size: 1.3em;">
{{ product.name }}
</h2>
{% if product.description %}
<p style="color: #666; margin-bottom: 15px;">{{ product.description }}</p>
{% endif %}
<a href="{{ url_for('submission.form', product_slug=product.submission_url_slug) }}"
class="btn"
style="display: inline-block;">
Submit Feedback
</a>
</div>
{% endfor %}
</div>
{% else %}
<div style="background-color: #fff3cd; padding: 20px; border-radius: 4px; border-left: 4px solid #ffc107; margin-top: 20px;">
<p style="margin: 0; color: #856404;">
No products are currently accepting feedback. Please check back later.
</p>
</div>
{% endif %}
{% endblock %}