# Quickstart: Product Selection Landing Page **Feature**: 002-product-list | **For**: Developers implementing this feature ## Overview Add a landing page at `/` that lists all active products for feedback submission. This is a simple addition to the existing Flask app: one route, one template, and corresponding tests. ## Implementation Checklist ### 1. Extend Product Model **File**: `app/models/product.py` **Add this class method**: ```python @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. """ all_products = cls.load_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)) ``` **Why**: Centralizes filtering and sorting logic. Reusable if other features need active product lists. --- ### 2. Create Landing Route **File**: `app/routes/landing.py` (new file) **Implementation**: ```python 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.""" try: products = Product.load_active() current_app.logger.info( f'Landing page accessed: {len(products)} active products' ) 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) return render_template('landing/index.html', products=[]) ``` **Register blueprint** in `app/__init__.py`: ```python from app.routes.landing import landing_bp app.register_blueprint(landing_bp) ``` --- ### 3. Create Landing Template **File**: `app/templates/landing/index.html` (new file) **Template structure**: ```html Select a Product - Reklamator

Select a Product for Feedback

{% if products %} {% else %}

No products are currently accepting feedback. Please check back later.

{% endif %} ``` **Key points**: - Jinja2 auto-escaping prevents XSS (product.name, product.description) - No JavaScript (pure server-side rendering) - Minimal inline CSS (no frameworks) - Conditional rendering for empty state - Only shows description if present (no placeholder text) --- ### 4. Write Contract Tests **File**: `tests/contract/test_landing_routes.py` (new file) **Test scenarios to implement**: ```python import pytest import os import yaml from app.models.product import Product @pytest.mark.contract def test_get_landing_page_with_products(client, temp_data_dir): """T301: GET / returns 200 with product list""" # Setup: Create 2 active products # Assert: 200 OK, both products in HTML pass @pytest.mark.contract def test_get_landing_page_no_products(client, temp_data_dir): """T302: GET / with no active products shows empty state""" # Assert: 200 OK, contains "No products are currently accepting feedback" pass @pytest.mark.contract def test_get_landing_page_filters_archived(client, temp_data_dir): """T303: GET / excludes archived products""" # Setup: 1 active, 1 archived # Assert: Only active product shown pass @pytest.mark.contract def test_get_landing_page_sorting(client, temp_data_dir): """T304: GET / sorts products alphabetically (name, then product_id)""" # Setup: Products with names "Zebra", "Apple", "apple" (different product_ids) # Assert: Correct alphabetical order pass @pytest.mark.contract def test_get_landing_page_xss_prevention(client, temp_data_dir): """T305: GET / escapes HTML in product names""" # Setup: Product with name "" # Assert: HTML is escaped, script not executed pass @pytest.mark.contract def test_get_landing_page_missing_slug(client, temp_data_dir): """T306: GET / excludes products with missing submission_url_slug""" # Setup: Product with submission_url_slug = None # Assert: Product not shown in list pass ``` --- ### 5. Write Integration Tests **File**: `tests/integration/test_landing_flow.py` (new file) **User journey test**: ```python @pytest.mark.integration def test_landing_to_submission_flow(client, temp_data_dir): """T307: Complete flow - landing page → product selection → submission form""" # Step 1: Visit landing page, see products # Step 2: Click product link # Step 3: Verify redirected to /submit/{slug} pass ``` --- ## Key Implementation Notes ### XSS Prevention - ✅ Jinja2 auto-escaping handles product names and descriptions - ✅ No manual HTML escaping needed - ✅ Test with `