diff --git a/src/porchlight/app.py b/src/porchlight/app.py index 9580200..acf3a70 100644 --- a/src/porchlight/app.py +++ b/src/porchlight/app.py @@ -146,4 +146,8 @@ def create_app(settings: Settings | None = None) -> FastAPI: async def health() -> dict[str, str]: return {"status": "ok"} + @app.get("/") + async def landing(request: Request): # type: ignore[no-untyped-def] + return templates.TemplateResponse(request, "index.html") + return app diff --git a/src/porchlight/static/style.css b/src/porchlight/static/style.css index cc7fa9c..7c32697 100644 --- a/src/porchlight/static/style.css +++ b/src/porchlight/static/style.css @@ -530,6 +530,66 @@ li { border: 0; } +/* ---------- Landing page ---------- */ + +.landing { + text-align: center; + padding-top: var(--sp-8); +} + +.landing-logo { + width: 6rem; + height: 6rem; + margin-bottom: var(--sp-6); +} + +.landing h1 { + margin-bottom: var(--sp-3); +} + +.landing-blurb { + color: var(--fg-muted); + font-size: var(--font-size-lg); + max-width: 30rem; + margin: 0 auto var(--sp-8); +} + +.landing-nav { + display: flex; + flex-direction: column; + gap: var(--sp-4); + max-width: 24rem; + margin: 0 auto; +} + +.landing-link { + display: block; + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: var(--sp-4) var(--sp-6); + text-decoration: none; + text-align: left; + transition: border-color 0.15s ease; +} + +.landing-link:hover { + border-color: var(--accent); + color: inherit; +} + +.landing-link strong { + display: block; + color: var(--fg); + font-size: var(--font-size-base); + margin-bottom: var(--sp-1); +} + +.landing-link span { + color: var(--fg-muted); + font-size: var(--font-size-sm); +} + /* ---------- Reduced motion ---------- */ @media (prefers-reduced-motion: reduce) { diff --git a/src/porchlight/templates/index.html b/src/porchlight/templates/index.html new file mode 100644 index 0000000..3555d83 --- /dev/null +++ b/src/porchlight/templates/index.html @@ -0,0 +1,19 @@ +{% extends "base.html" %} + +{% block content %} +
+ +

Porchlight

+

OpenID Connect Provider with user management. Manage your identity, credentials, and connected applications.

+ +
+{% endblock %} diff --git a/tests/test_app.py b/tests/test_app.py index 862e56d..779c417 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -28,6 +28,18 @@ async def test_app_has_repos_on_state(client: AsyncClient) -> None: assert isinstance(app.state.magic_link_repo, MagicLinkRepository) +async def test_landing_page(client: AsyncClient) -> None: + response = await client.get("/") + assert response.status_code == 200 + assert "text/html" in response.headers["content-type"] + body = response.text + assert "

Porchlight

" in body + assert "/manage/profile" in body + assert "/admin/users" in body + assert "My Account" in body + assert "Administration" in body + + async def test_dependency_functions() -> None: from unittest.mock import MagicMock