efihub/docs/superpowers/plans/2026-04-27-landing-page.md

10 KiB
Raw Blame History

Landing Page & Shared Layout Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Build a shared Jinja2/Tailwind CSS layout and a module-card landing page for the University Process Hub.

Architecture: FastAPI serves HTML via Jinja2Templates. base.html defines the red navbar (#e2001a), a {% block content %} slot, and a footer. index.html extends it with a hero section, responsive 3-column module-card grid, and an info strip. Module metadata is a static list in main.py — no DB access on the landing page.

Tech Stack: FastAPI, Jinja2, Tailwind CSS Play CDN (no build step), pytest, httpx


Task 1: Add dependencies and update .gitignore

Files:

  • Modify: requirements.txt

  • Modify: .gitignore

  • Step 1: Update requirements.txt

fastapi
uvicorn[standard]
pydantic-settings
sqlalchemy
jinja2
pytest
httpx
  • Step 2: Add .superpowers/ to .gitignore

Append to the # Project specific section:

.superpowers/
  • Step 3: Install new dependencies
.venv/bin/pip install -r requirements.txt

Expected: jinja2, pytest, httpx installed without error.

  • Step 4: Commit
git add requirements.txt .gitignore
git commit -m "chore: add jinja2 and test dependencies"

Task 2: Create base layout template

Files:

  • Create: app/templates/base.html

  • Create: app/static/.gitkeep

  • Step 1: Create directories

mkdir -p app/templates app/static
touch app/static/.gitkeep
  • Step 2: Write app/templates/base.html
<!DOCTYPE html>
<html lang="de">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>{% block title %}EFI Hub{% endblock %}</title>
  <script src="https://cdn.tailwindcss.com"></script>
  <script>
    tailwind.config = {
      theme: {
        extend: {
          colors: { 'efi': '#e2001a' }
        }
      }
    }
  </script>
</head>
<body class="bg-gray-100 min-h-screen flex flex-col">

  <nav class="bg-efi h-14 flex items-center px-8 shrink-0">
    <a href="/" class="text-white font-bold text-lg tracking-tight mr-8">
      EFI<span class="opacity-70 font-normal">Hub</span>
    </a>
    {% for item in nav_items %}
    <a href="{{ item.url }}"
       class="text-white/85 hover:text-white text-sm px-4 h-14 flex items-center border-b-2
              {% if item.active %}border-white text-white{% else %}border-transparent{% endif %}">
      {{ item.label }}
    </a>
    {% endfor %}
    <div class="ml-auto">
      <button class="text-white text-sm px-4 py-1.5 rounded border border-white/40 bg-white/15 hover:bg-white/25">
        Anmelden
      </button>
    </div>
  </nav>

  <main class="flex-1">
    {% block content %}{% endblock %}
  </main>

  <footer class="bg-gray-100 border-t border-gray-300 px-8 py-3.5 flex items-center justify-between text-xs text-gray-400 shrink-0">
    <span class="font-semibold text-gray-500">EFI Hub</span>
    <span>Technische Hochschule Nürnberg Georg Simon Ohm · Fakultät EFI</span>
    <span>v{{ app_version }}</span>
  </footer>

</body>
</html>
  • Step 3: Commit
git add app/templates/base.html app/static/.gitkeep
git commit -m "feat: add base layout template with red navbar and footer"

Task 3: Create landing page template

Files:

  • Create: app/templates/index.html

  • Step 1: Write app/templates/index.html

{% extends "base.html" %}

{% block title %}University Process Hub{% endblock %}

{% block content %}

<div class="bg-white border-b border-gray-200 px-8 py-10">
  <p class="text-xs font-semibold uppercase tracking-widest text-efi mb-2">
    Fakultät EFI · TH Nürnberg
  </p>
  <h1 class="text-2xl font-bold text-gray-900 mb-2">University Process Hub</h1>
  <p class="text-sm text-gray-500 max-w-xl leading-relaxed">
    Modulares Informationssystem für Fakultätsprozesse.
    Startpunkt für studentische Arbeiten und Schnittstelle für interne Dienste.
  </p>
</div>

<div class="px-8 py-7">
  <p class="text-xs font-semibold uppercase tracking-widest text-gray-400 mb-4">
    Verfügbare Module
  </p>
  <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">

    {% for module in modules %}
    <div class="bg-white rounded-md border border-gray-200 shadow-sm p-5 relative overflow-hidden
                hover:shadow-md hover:border-efi transition-all duration-150 cursor-pointer">
      <div class="absolute top-0 left-0 right-0 h-0.5 bg-efi"></div>
      <div class="text-2xl mb-2">{{ module.icon }}</div>
      <div class="font-semibold text-gray-900 text-sm mb-1">{{ module.name }}</div>
      <div class="text-xs text-gray-500 leading-relaxed mb-3">{{ module.description }}</div>
      {% if module.status == "active" %}
        <span class="text-xs font-semibold px-2 py-0.5 rounded-full bg-green-50 text-green-700 border border-green-200">Aktiv</span>
      {% else %}
        <span class="text-xs font-semibold px-2 py-0.5 rounded-full bg-gray-100 text-gray-500 border border-gray-200">Geplant</span>
      {% endif %}
    </div>
    {% endfor %}

    <div class="bg-white rounded-md border border-dashed border-gray-300 p-5">
      <div class="text-2xl mb-2 opacity-30"></div>
      <div class="font-semibold text-gray-300 text-sm mb-1">Neues Modul</div>
      <div class="text-xs text-gray-300 leading-relaxed">Erweiterbar durch studentische Arbeiten.</div>
    </div>

  </div>
</div>

<div class="bg-white border-t border-gray-200 px-8 py-4 flex flex-wrap items-center gap-8">
  <span class="text-xs text-gray-500">API: <strong class="text-gray-800">FastAPI</strong></span>
  <span class="text-xs text-gray-500">Datenbank: <strong class="text-gray-800">{{ db_mode }}</strong></span>
  <span class="text-xs text-gray-500">WebSocket: <strong class="text-gray-800">aktiv</strong></span>
  <div class="ml-auto text-xs font-medium text-efi">● System online</div>
</div>

{% endblock %}
  • Step 2: Commit
git add app/templates/index.html
git commit -m "feat: add landing page template with module grid and info strip"

Task 4: Wire templates into FastAPI and write tests

Files:

  • Modify: app/main.py

  • Create: tests/__init__.py

  • Create: tests/test_landing.py

  • Step 1: Write the failing tests

mkdir -p tests && touch tests/__init__.py

Schreibe tests/test_landing.py:

from fastapi.testclient import TestClient
from app.main import app

client = TestClient(app)


def test_landing_returns_html():
    response = client.get("/")
    assert response.status_code == 200
    assert "text/html" in response.headers["content-type"]


def test_landing_contains_title():
    response = client.get("/")
    assert "University Process Hub" in response.text


def test_landing_contains_rss_module():
    response = client.get("/")
    assert "RSS-Feed Server" in response.text


def test_landing_navbar_links_present():
    response = client.get("/")
    assert "Übersicht" in response.text
    assert "RSS-Feeds" in response.text


def test_landing_info_strip_shows_db_mode():
    response = client.get("/")
    assert "SQLite" in response.text or "MariaDB" in response.text
  • Step 2: Run tests to confirm they fail
.venv/bin/pytest tests/test_landing.py -v

Expected: FAIL — GET / returns JSON, not HTML.

  • Step 3: Replace app/main.py with template-serving version
from fastapi import FastAPI, Request, WebSocket
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates

from app.core.config import get_settings

settings = get_settings()

app = FastAPI(
    title="University Process Hub",
    root_path=settings.APP_PREFIX,
)

app.mount("/static", StaticFiles(directory="app/static"), name="static")
templates = Jinja2Templates(directory="app/templates")

MODULES = [
    {
        "icon": "📡",
        "name": "RSS-Feed Server",
        "description": "Aggregiert und verteilt Neuigkeiten der Fakultät als standardkonformen RSS 2.0 Feed.",
        "status": "active",
    },
    {
        "icon": "📅",
        "name": "Kalender",
        "description": "Veranstaltungen, Prüfungstermine und Fristen im iCal-Format.",
        "status": "planned",
    },
    {
        "icon": "🔔",
        "name": "Benachrichtigungen",
        "description": "Push-Nachrichten und WebSocket-basierte Echtzeit-Alerts für Studierende.",
        "status": "planned",
    },
    {
        "icon": "📊",
        "name": "Auslastung",
        "description": "Raum- und Ressourcenauslastung der Fakultät in Echtzeit.",
        "status": "planned",
    },
    {
        "icon": "📚",
        "name": "Lehrveranstaltungen",
        "description": "Stundenplan-API und Kursinformationen aus dem Campus-System.",
        "status": "planned",
    },
]

NAV_ITEMS = [
    {"label": "Übersicht", "url": "/", "active": True},
    {"label": "RSS-Feeds", "url": "/rss", "active": False},
    {"label": "Kalender", "url": "/kalender", "active": False},
    {"label": "Docs", "url": "/docs", "active": False},
]


def _db_mode() -> str:
    url = settings.DATABASE_URL
    return "SQLite (dev)" if url.startswith("sqlite") else "MariaDB (prod)"


@app.get("/", response_class=HTMLResponse)
async def root(request: Request):
    return templates.TemplateResponse(
        request,
        "index.html",
        {
            "nav_items": NAV_ITEMS,
            "modules": MODULES,
            "db_mode": _db_mode(),
            "app_version": "0.1.0",
        },
    )


@app.websocket("/ws/hello/{name}")
async def websocket_hello(websocket: WebSocket, name: str):
    await websocket.accept()
    await websocket.send_text(f"Hello, {name}!")
    await websocket.close()
  • Step 4: Run tests to confirm they pass
.venv/bin/pytest tests/test_landing.py -v

Expected: All 5 tests PASS.

  • Step 5: Verify in browser
.venv/bin/uvicorn app.main:app --reload

Öffne http://localhost:8000 — Navbar rot, Hero-Bereich, 5 Modul-Karten + Placeholder-Karte, Info-Strip, Footer.

  • Step 6: Commit
git add app/main.py tests/
git commit -m "feat: serve landing page via Jinja2 with module grid"