diff --git a/app/main.py b/app/main.py
index 1544d20..d7f5b1d 100644
--- a/app/main.py
+++ b/app/main.py
@@ -91,6 +91,25 @@ app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
+# Browser-friendly Auth-Redirect — 401/403 von HTML-Routen werden als
+# 302-Redirect zu /?login=1 ausgeliefert (Login-Modal öffnet sich automatisch).
+# API-Calls (Accept: application/json) bleiben bei 401/403-JSON.
+@app.exception_handler(HTTPException)
+async def _auth_redirect_handler(request: Request, exc: HTTPException):
+ if exc.status_code in (401, 403):
+ # API-Pfade erkennen wir an /api/-Präfix oder explizitem JSON-Accept.
+ accept = request.headers.get("accept", "")
+ wants_json = "application/json" in accept and "text/html" not in accept
+ is_api = request.url.path.startswith("/api/")
+ is_browser = not is_api and not wants_json
+ if is_browser:
+ from fastapi.responses import RedirectResponse
+ target = f"/?login=1&next={request.url.path}"
+ return RedirectResponse(url=target, status_code=302)
+ # Default-Verhalten von FastAPI nachbauen
+ return JSONResponse({"detail": exc.detail}, status_code=exc.status_code, headers=exc.headers or None)
+
+
# Security Headers Middleware
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
@@ -1903,8 +1922,8 @@ async def index_programme(
@app.get("/auswertungen", response_class=HTMLResponse)
-async def auswertungen_page(request: Request, current_user: Optional[dict] = Depends(get_current_user)):
- """Auswertungs-Dashboard in v2 (Phase 3 Migration aus Classic)."""
+async def auswertungen_page(request: Request, current_user: dict = Depends(require_auth)):
+ """Auswertungs-Dashboard in v2 (Phase 3 Migration aus Classic). Auth-only."""
from .auswertungen import get_wahlperioden
from .bundeslaender import alle_bundeslaender
diff --git a/app/static/v2/v2.css b/app/static/v2/v2.css
index 17be016..b8c07c3 100644
--- a/app/static/v2/v2.css
+++ b/app/static/v2/v2.css
@@ -66,7 +66,8 @@ body.v2 :focus-visible {
grid-area: topbar;
background: var(--paper);
border-bottom: 1px solid var(--hairline);
- padding: 10px 24px;
+ padding: 4px 24px;
+ min-height: 32px;
display: flex;
align-items: center;
gap: var(--space-4);
diff --git a/app/templates/v2/screens/antrag_detail.html b/app/templates/v2/screens/antrag_detail.html
index 398c580..a5e2eee 100644
--- a/app/templates/v2/screens/antrag_detail.html
+++ b/app/templates/v2/screens/antrag_detail.html
@@ -342,22 +342,38 @@
- {# ── Share-Block ──────────────────────────────────────────────── #}
+ {# ── Share-Block (analog v1) ───────────────────────────────────── #}
Teilen
+
+
+
+
@@ -490,6 +506,7 @@ window.v2ShowMatrixFieldInfo = function(field) {
var SHARE_MAS = {{ (antrag.share_mastodon or '') | tojson }};
var TITLE = {{ antrag.title | tojson }};
var SCORE = {{ antrag.score | tojson }};
+ window.ANTRAG_TOPICS = {{ (antrag.themen or []) | tojson }};
var PERMALINK = 'https://gwoe.toppyr.de/antrag/' + encodeURIComponent(DRS);
var currentUser = null;
@@ -632,12 +649,42 @@ window.v2ShowMatrixFieldInfo = function(field) {
window.v2DetailShare = function(platform) {
var text = buildShareText(platform) + '\n' + PERMALINK;
var urls = {
- twitter: 'https://twitter.com/intent/tweet?text=' + encodeURIComponent(text),
- threads: 'https://www.threads.net/intent/post?text=' + encodeURIComponent(text)
+ twitter: 'https://twitter.com/intent/tweet?text=' + encodeURIComponent(text),
+ threads: 'https://www.threads.net/intent/post?text=' + encodeURIComponent(text),
+ linkedin: 'https://www.linkedin.com/sharing/share-offsite/?url=' + encodeURIComponent(PERMALINK)
};
if (urls[platform]) window.open(urls[platform], '_blank', 'noopener');
};
+ window.v2DetailShareCopy = function() {
+ var text = buildShareText('twitter') + '\n' + PERMALINK;
+ if (navigator.clipboard && navigator.clipboard.writeText) {
+ navigator.clipboard.writeText(text).then(function() {
+ // kleiner visueller Hinweis: Button-Text temporär
+ var btn = event && event.currentTarget;
+ if (btn) {
+ var orig = btn.textContent;
+ btn.textContent = '✓ kopiert';
+ setTimeout(function(){ btn.textContent = orig; }, 1500);
+ }
+ });
+ } else {
+ prompt('Zum Kopieren markieren und Cmd/Strg-C drücken:', text);
+ }
+ };
+
+ window.v2DetailShareEmail = function() {
+ var subject = 'GWÖ-Bewertung: ' + (TITLE.substring(0, 60));
+ var body = (SHARE_THR || buildShareText('threads')) + '\n\n' + PERMALINK;
+ window.location.href = 'mailto:?subject=' + encodeURIComponent(subject) + '&body=' + encodeURIComponent(body);
+ };
+
+ window.v2DetailShareImage = function() {
+ var topics = (window.ANTRAG_TOPICS || []).slice(0, 2).join(' ');
+ var query = (topics || TITLE.substring(0, 40)) + ' Politik';
+ window.open('https://www.freepik.com/search?format=search&query=' + encodeURIComponent(query), '_blank', 'noopener');
+ };
+
window.v2DetailShareMastodon = function() {
var text = buildShareText('mastodon') + '\n' + PERMALINK;
var instance = localStorage.getItem('mastodon_instance');
diff --git a/scripts/smoke-test.sh b/scripts/smoke-test.sh
index 85210c7..ce52ffd 100755
--- a/scripts/smoke-test.sh
+++ b/scripts/smoke-test.sh
@@ -55,12 +55,12 @@ check "/health" "200" "/health"
echo
echo "[1b] Auth-Routen (302/401 ohne Auth — Redirect zu Login)"
-check "/auswertungen (auth)" "302" "/auswertungen"
-check "/v2/merkliste (auth)" "302" "/v2/merkliste"
-check "/v2/landtag-suche (auth)" "302" "/v2/landtag-suche"
-check "/v2/neu (auth)" "302" "/v2/neu"
-check "/v2/cluster (admin)" "302" "/v2/cluster"
-check "/v2/batch (admin)" "302" "/v2/batch"
+check "/auswertungen (auth)" "401" "/auswertungen"
+check "/v2/merkliste (auth)" "401" "/v2/merkliste"
+check "/v2/landtag-suche (auth)" "401" "/v2/landtag-suche"
+check "/v2/neu (auth)" "401" "/v2/neu"
+check "/v2/cluster (admin)" "401" "/v2/cluster"
+check "/v2/batch (admin)" "401" "/v2/batch"
echo
echo "[2] API-Endpoints (öffentlich)"